Imported Upstream version 3.2.0
[debian/amanda] / recover-src / extract_list.c
index 72b37c52bc0a86ced1762a1d084ff68b5b1c94e1..baac50775b4705014b229ac80b813ffb2a5f6e05 100644 (file)
  * file named AUTHORS, in the root directory of this distribution.
  */
 /*
- * $Id: extract_list.c,v 1.97 2006/03/09 16:51:41 martinea Exp $
+ * $Id$
  *
  * implements the "extract" command in amrecover
  */
 
 #include "amanda.h"
-#include "version.h"
+#include "match.h"
 #include "amrecover.h"
 #include "fileheader.h"
 #include "dgram.h"
 #include "findpass.h"
 #endif
 #include "util.h"
+#include "conffile.h"
+#include "protocol.h"
+#include "event.h"
+#include "client_util.h"
+#include "security.h"
 
-typedef struct EXTRACT_LIST_ITEM
-{
+typedef struct EXTRACT_LIST_ITEM {
     char *path;
-
     struct EXTRACT_LIST_ITEM *next;
 }
 EXTRACT_LIST_ITEM;
 
-typedef struct EXTRACT_LIST
-{
+typedef struct EXTRACT_LIST {
     char *date;                        /* date tape created */
-    int  level;                                /* level of dump */
+    int  level;                        /* level of dump */
     char *tape;                        /* tape label */
-    int fileno;                                /* fileno on tape */
-    EXTRACT_LIST_ITEM *files;          /* files to get off tape */
+    off_t fileno;              /* fileno on tape */
+    EXTRACT_LIST_ITEM *files;  /* files to get off tape */
 
     struct EXTRACT_LIST *next;
 }
 EXTRACT_LIST;
 
+typedef struct ctl_data_s {
+  int                      header_done;
+  int                      child_pipe[2];
+  int                      pid;
+  EXTRACT_LIST            *elist;
+  dumpfile_t               file;
+  data_path_t              data_path;
+  char                    *addrs;
+  backup_support_option_t *bsu;
+  gint64                   bytes_read;
+} ctl_data_t;
+
 #define SKIP_TAPE 2
 #define RETRY_TAPE 3
 
-char *dump_device_name = NULL;
-
+static struct {
+    const char *name;
+    security_stream_t *fd;
+} amidxtaped_streams[] = {
+#define CTLFD  0
+    { "CTL", NULL },
+#define DATAFD  1
+    { "DATA", NULL },
+};
+#define NSTREAMS  (int)(sizeof(amidxtaped_streams) / sizeof(amidxtaped_streams[0]))
+
+
+static void amidxtaped_response(void *, pkt_t *, security_handle_t *);
+static void stop_amidxtaped(void);
+static char *dump_device_name = NULL;
+static char *errstr;
+static char *amidxtaped_line = 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;
+static const security_driver_t *amidxtaped_secdrv;
 
-#ifdef SAMBA_CLIENT
 unsigned short samba_extract_method = SAMBA_TAR;
-#endif /* SAMBA_CLIENT */
 
 #define READ_TIMEOUT   240*60
 
-static int okay_to_continue P((int, int,  int));
-void writer_intermediary P((int ctl_fd, int data_fd, EXTRACT_LIST *elist));
-
+EXTRACT_LIST *first_tape_list(void);
+EXTRACT_LIST *next_tape_list(EXTRACT_LIST *list);
+static int is_empty_dir(char *fname);
+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 check_file_overwrite(char *filename);
+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);
+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 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(ctl_data_t *ctl_data);
+static void send_to_tape_server(security_stream_t *stream, char *cmd);
+int writer_intermediary(EXTRACT_LIST *elist);
+int get_amidxtaped_line(void);
+static void read_amidxtaped_data(void *, void *, ssize_t);
+static char *merge_path(char *path1, char *path2);
+static gboolean ask_file_overwrite(ctl_data_t *ctl_data);
+static void start_processing_data(ctl_data_t *ctl_data);
 
 /*
  * Function:  ssize_t read_buffer(datafd, buffer, buflen, timeout_s)
@@ -107,25 +171,26 @@ void writer_intermediary P((int ctl_fd, int data_fd, EXTRACT_LIST *elist));
  */
 
 static ssize_t
-read_buffer(datafd, buffer, buflen, timeout_s)
-int datafd;
-char *buffer;
-size_t buflen;
+read_buffer(
+    int                datafd,
+    char *     buffer,
+    size_t     buflen,
+    long       timeout_s)
 {
     ssize_t size = 0;
-    fd_set readset;
+    SELECT_ARG_TYPE readset;
     struct timeval timeout;
     char *dataptr;
-    size_t spaceleft;
+    ssize_t spaceleft;
     int nfound;
 
-    if(datafd < 0 || datafd >= FD_SETSIZE) {
+    if(datafd < 0 || datafd >= (int)FD_SETSIZE) {
        errno = EMFILE;                                 /* out of range */
        return -1;
     }
 
     dataptr = buffer;
-    spaceleft = buflen;
+    spaceleft = (ssize_t)buflen;
 
     do {
         FD_ZERO(&readset);
@@ -135,7 +200,7 @@ size_t buflen;
         nfound = select(datafd+1, &readset, NULL, NULL, &timeout);
         if(nfound < 0 ) {
             /* Select returned an error. */
-           fprintf(stderr,"select error: %s\n", strerror(errno));
+           g_fprintf(stderr,_("select error: %s\n"), strerror(errno));
             size = -1;
            break;
         }
@@ -144,8 +209,8 @@ size_t buflen;
             /* 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");
+                g_fprintf(stderr,_("timeout waiting for restore\n"));
+                g_fprintf(stderr,_("increase READ_TIMEOUT in recover-src/extract_list.c if your tape is slow\n"));
             }
             errno = ETIMEDOUT;
             size = -1;
@@ -156,13 +221,13 @@ size_t buflen;
            continue;
 
         /* Select says data is available, so read it.  */
-        size = read(datafd, dataptr, spaceleft);
+        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",
+               g_fprintf(stderr, _("read_buffer: read error - %s"),
                    strerror(errno));
                break;
            }
@@ -172,28 +237,31 @@ size_t buflen;
         dataptr += size;
     } while ((size > 0) && (spaceleft > 0));
 
-    return (((buflen-spaceleft) > 0) ? (buflen-spaceleft) : size);
+    return ((((ssize_t)buflen-spaceleft) > 0) ? ((ssize_t)buflen-spaceleft) : size);
 }
 
 
-EXTRACT_LIST *first_tape_list P((void))
+EXTRACT_LIST *
+first_tape_list(void)
 {
     return extract_list;
 }
 
-EXTRACT_LIST *next_tape_list(list)
-EXTRACT_LIST *list;
+EXTRACT_LIST *
+next_tape_list(
+    /*@keep@*/EXTRACT_LIST *list)
 {
     if (list == NULL)
        return NULL;
     return list->next;
 }
 
-static void clear_tape_list(tape_list)
-EXTRACT_LIST *tape_list;
+static void
+clear_tape_list(
+    EXTRACT_LIST *     tape_list)
 {
     EXTRACT_LIST_ITEM *this, *next;
-    
+
 
     this = tape_list->files;
     while (this != NULL)
@@ -209,8 +277,9 @@ EXTRACT_LIST *tape_list;
 
 /* remove a tape list from the extract list, clearing the tape list
    beforehand if necessary */
-void delete_tape_list(tape_list)
-EXTRACT_LIST *tape_list;
+void
+delete_tape_list(
+    EXTRACT_LIST *tape_list)
 {
     EXTRACT_LIST *this, *prev;
 
@@ -250,8 +319,9 @@ EXTRACT_LIST *tape_list;
 
 
 /* return the number of files on a tape's list */
-int length_of_tape_list(tape_list)
-EXTRACT_LIST *tape_list;
+int
+length_of_tape_list(
+    EXTRACT_LIST *tape_list)
 {
     EXTRACT_LIST_ITEM *fn;
     int n;
@@ -264,18 +334,248 @@ EXTRACT_LIST *tape_list;
 }
 
 
-void clear_extract_list P((void))
+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;
+       }
+    }
+}
+
+
+static char *
+file_of_path(
+    char *path,
+    char **dir)
+{
+    char *npath = g_path_get_basename(path);
+    *dir = g_path_get_dirname(path);
+    if (strcmp(*dir, ".") == 0) {
+       amfree(*dir);
+    }
+    return npath;
+}
+
+void
+clean_extract_list(void)
+{
+    EXTRACT_LIST *this;
+
+    for (this = extract_list; this != NULL; this = this->next)
+       clean_tape_list(this);
+}
+
+
+int add_to_unlink_list(char *path);
+int do_unlink_list(void);
+void free_unlink_list(void);
+
+typedef struct s_unlink_list {
+    char *path;
+    struct s_unlink_list *next;
+} t_unlink_list;
+t_unlink_list *unlink_list = NULL;
+
+int
+add_to_unlink_list(
+    char *path)
+{
+    t_unlink_list *ul;
+
+    if (!unlink_list) {
+       unlink_list = alloc(SIZEOF(*unlink_list));
+       unlink_list->path = stralloc(path);
+       unlink_list->next = NULL;
+    } else {
+       for (ul = unlink_list; ul != NULL; ul = ul->next) {
+           if (strcmp(ul->path, path) == 0)
+               return 0;
+       }
+       ul = alloc(SIZEOF(*ul));
+       ul->path = stralloc(path);
+       ul->next = unlink_list;
+       unlink_list = ul;
+    }
+    return 1;
+}
+
+int
+do_unlink_list(void)
+{
+    t_unlink_list *ul;
+    int ret = 1;
+
+    for (ul = unlink_list; ul != NULL; ul = ul->next) {
+       if (unlink(ul->path) < 0) {
+           g_fprintf(stderr,_("Can't unlink %s: %s\n"), ul->path, strerror(errno));
+           ret = 0;
+       }
+    }
+    return ret;
+}
+
+
+void
+free_unlink_list(void)
+{
+    t_unlink_list *ul, *ul1;
+
+    for (ul = unlink_list; ul != NULL; ul = ul1) {
+       amfree(ul->path);
+       ul1 = ul->next;
+       amfree(ul);
+    }
+
+    unlink_list = NULL;
+}
+
+
+
+void
+check_file_overwrite(
+    char *dir)
+{
+    EXTRACT_LIST      *this;
+    EXTRACT_LIST_ITEM *fn;
+    struct stat        stat_buf;
+    char              *filename;
+    char              *path, *s;
+
+    for (this = extract_list; this != NULL; this = this->next) {
+       for (fn = this->files; fn != NULL ; fn = fn->next) {
+
+           /* Check path component of fn->path */
+
+           path = stralloc2(dir, fn->path);
+           if (path[strlen(path)-1] == '/') {
+               path[strlen(path)-1] = '\0';
+           }
+
+           s = path + strlen(dir) + 1;
+           while((s = strchr(s, '/'))) {
+               *s = '\0';
+               if (lstat(path, &stat_buf) == 0) {
+                   if(!S_ISDIR(stat_buf.st_mode)) {
+                       if (add_to_unlink_list(path)) {
+                           g_printf(_("WARNING: %s is not a directory, "
+                                  "it will be deleted.\n"),
+                                  path);
+                       }
+                   }
+               }
+               else if (errno != ENOENT) {
+                   g_printf(_("Can't stat %s: %s\n"), path, strerror(errno));
+               }
+               *s = '/';
+               s++;
+           }
+           amfree(path);
+
+           /* Check fn->path */
+
+           filename = stralloc2(dir, fn->path);
+           if (filename[strlen(filename)-1] == '/') {
+               filename[strlen(filename)-1] = '\0';
+           }
+
+           if (lstat(filename, &stat_buf) == 0) {
+               if(S_ISDIR(stat_buf.st_mode)) {
+                   if(!is_empty_dir(filename)) {
+                       g_printf(_("WARNING: All existing files in %s "
+                              "will be deleted.\n"), filename);
+                   }
+               } else if(S_ISREG(stat_buf.st_mode)) {
+                   g_printf(_("WARNING: Existing file %s will be overwritten\n"),
+                          filename);
+               } else {
+                   if (add_to_unlink_list(filename)) {
+                       g_printf(_("WARNING: Existing entry %s will be deleted\n"),
+                              filename);
+                   }
+               }
+           } else if (errno != ENOENT) {
+               g_printf(_("Can't stat %s: %s\n"), filename, strerror(errno));
+           }
+           amfree(filename);
+       }
+    }
+}
+
+
 /* returns -1 if error */
 /* returns  0 on succes */
 /* returns  1 if already added */
-static int add_extract_item(ditem)
-DIR_ITEM *ditem;
+static int
+add_extract_item(
+    DIR_ITEM *ditem)
 {
     EXTRACT_LIST *this, *this1;
     EXTRACT_LIST_ITEM *that, *curr;
@@ -322,7 +622,7 @@ DIR_ITEM *ditem;
     /* 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) 
+    if(extract_list==NULL || strcmp(this->date,extract_list->date) < 0)
     {
        this->next = extract_list;
        extract_list = this;
@@ -351,8 +651,9 @@ DIR_ITEM *ditem;
 /* returns -1 if error */
 /* returns  0 on deletion */
 /* returns  1 if not there */
-static int delete_extract_item(ditem)
-DIR_ITEM *ditem;
+static int
+delete_extract_item(
+    DIR_ITEM *ditem)
 {
     EXTRACT_LIST *this;
     EXTRACT_LIST_ITEM *that, *prev;
@@ -404,127 +705,201 @@ DIR_ITEM *ditem;
     return 1;
 }
 
+static char *
+merge_path(
+    char *path1,
+    char *path2)
+{
+    char *result;
+    int len = strlen(path1);
+    if (path1[len-1] == '/' && path2[0] == '/') {
+       result = stralloc2(path1, path2+1);
+    } else if (path1[len-1] != '/' && path2[0] != '/') {
+       result = vstralloc(path1, "/", path2, NULL);
+    } else {
+       result = stralloc2(path1, path2);
+    }
+    return result;
+}
 
-void add_glob(glob)
-char *glob;
+void
+add_glob(
+    char *     glob)
 {
     char *regex;
     char *regex_path;
     char *s;
+    char *uqglob;
+    char *dir;
+    char *sdir = NULL;
+    int   result = 1;
 
-    regex = glob_to_regex(glob);
-    dbprintf(("add_glob (%s) -> %s\n", glob, regex));
-    if ((s = validate_regexp(regex)) != NULL) {
-       printf("\"%s\" is not a valid shell wildcard pattern: ", glob);
-       puts(s);
+    if (disk_path == NULL) {
+       g_printf(_("Must select directory before adding files\n"));
        return;
     }
-    /*
-     * 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);
-    amfree(regex);
-    regex_path[strlen(regex_path) - 1] = '\0';
-    strappend(regex_path, "[/]*$");
-    add_file(glob, regex_path);
-    amfree(regex_path);
+
+    uqglob = unquote_string(glob);
+    glob = file_of_path(uqglob, &dir);
+    if (dir) {
+       sdir = merge_path(mount_point, disk_path);
+       result = cd_glob(dir, 0);
+       amfree(dir);
+    }
+    if (result) {
+       regex = glob_to_regex(glob);
+       dbprintf(_("add_glob (%s) -> %s\n"), uqglob, regex);
+       if ((s = validate_regexp(regex)) != NULL) {
+           g_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);
+       }
+       if (sdir) {
+           set_directory(sdir, 0);
+       }
+       amfree(regex);
+    }
+    amfree(sdir);
+    amfree(uqglob);
+    amfree(glob);
 }
 
-void add_regex(regex)
-char *regex;
+void
+add_regex(
+    char *     regex)
 {
     char *s;
+    char *dir;
+    char *sdir = NULL;
+    char *uqregex;
+    char *newregex;
+    int   result = 1;
 
-    if ((s = validate_regexp(regex)) != NULL) {
-       printf("\"%s\" is not a valid regular expression: ", regex);
-       puts(s);
+    if (disk_path == NULL) {
+       g_printf(_("Must select directory before adding files\n"));
        return;
     }
-    add_file(regex, regex);
+
+    uqregex = unquote_string(regex);
+    newregex = file_of_path(uqregex, &dir);
+    if (dir) {
+       sdir = merge_path(mount_point, disk_path);
+       result = cd_regex(dir, 0);
+       amfree(dir);
+    }
+
+    if (result) { 
+       if ((s = validate_regexp(newregex)) != NULL) {
+           g_printf(_("\"%s\" is not a valid regular expression: "), newregex);
+           puts(s);
+       } else {
+            add_file(uqregex, newregex);
+       }
+       if (sdir) {
+           set_directory(sdir, 0);
+       }
+    }
+    amfree(sdir);
+    amfree(uqregex);
+    amfree(newregex);
 }
 
-void add_file(path, regex)
-char *path;
-char *regex;
+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;
-    int j;
+    ssize_t j;
     char *dir, *dir_undo, dir_undo_ch = '\0';
     char *ditem_path = NULL;
+    char *qditem_path = NULL;
     char *l = NULL;
     int  added;
-    char *s, *fp;
+    char *s, *fp, *quoted;
     int ch;
     int found_one;
+    int dir_entries;
 
     if (disk_path == NULL) {
-       printf("Must select directory before adding files\n");
+       g_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));
+    dbprintf(_("add_file: Looking for \"%s\"\n"), regex);
 
-    /* remove "/" at end of path */
-    j = strlen(regex)-1;
-    while(j >= 0 && regex[j] == '/') regex[j--] = '\0';
+    if(strcmp(regex, "/[/]*$") == 0) { /* "/" behave like "." */
+       regex = "\\.[/]*$";
+    }
+    else if(strcmp(regex, "[^/]*[/]*$") == 0) {                /* "*" */
+       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 everything in directory... */
-               path_on_disk = stralloc("/[^/]*[/]*$");
-           } else {
-               /* No mods needed if already starts with '/' */
-               path_on_disk = stralloc(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);
+       char *clean_disk_path = clean_regex(disk_path, 0);
        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));
+    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))
     {
-       dbprintf(("add_file: Pondering ditem->path=\"%s\"\n", ditem->path));
-       if (match(path_on_disk, ditem->path)
-           || match(path_on_disk_slash, ditem->path))
+       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))
        {
            found_one = 1;
-           j = strlen(ditem->path);
+           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);
+               qditem_path = quote_string(ditem_path);
+               cmd = newstralloc2(cmd, "ORLD ", qditem_path);
+               amfree(qditem_path);
                if(send_command(cmd) == -1) {
                    amfree(cmd);
                    amfree(ditem_path);
                    amfree(path_on_disk);
-                   amfree(path_on_disk_slash);
                    exit(1);
                }
                amfree(cmd);
@@ -533,28 +908,24 @@ char *regex;
                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 */
-               {
+               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);
+                   g_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)
-               {
+
+               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) {
@@ -570,76 +941,83 @@ char *regex;
                        puts(l);
                        continue;
                    }
-#define sc "201-"
-                   if(strncmp(l, sc, sizeof(sc)-1) != 0) {
-                       err = "bad reply: not 201-";
+
+                   s = l;
+                   if(strncmp_const_skip(l, "201-", s, ch) != 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";
+                       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] = ch;
+                    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";
+                       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";
+                       err = _("bad reply: missing tape field");
                        continue;
                    }
                     fp = s-1;
-                    skip_non_whitespace(s, ch);
+                    skip_quoted_string(s, ch);
                     s[-1] = '\0';
-                    lditem.tape = newstralloc(lditem.tape, fp);
-                    s[-1] = ch;
+                   amfree(lditem.tape);
+                   lditem.tape = unquote_string(fp);
+                    s[-1] = (char)ch;
 
                    if(am_has_feature(indexsrv_features, fe_amindexd_fileno_in_ORLD)) {
+                       long long fileno_ = (long long)0;
                        skip_whitespace(s, ch);
-                       if(ch == '\0' || sscanf(s - 1, "%d", &lditem.fileno) != 1) {
-                           err = "bad reply: cannot parse fileno field";
+                       if(ch == '\0' ||
+                          sscanf(s - 1, "%lld", &fileno_) != 1) {
+                           err = _("bad reply: cannot parse fileno field");
                            continue;
                        }
+                       lditem.fileno = (off_t)fileno_;
                        skip_integer(s, ch);
                    }
 
                    skip_whitespace(s, ch);
                    if(ch == '\0') {
-                       err = "bad reply: missing directory field";
+                       err = _("bad reply: missing directory field");
                        continue;
                    }
                    dir = s - 1;
-                   skip_non_whitespace(s, ch);
+                   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"));
+                       g_printf(_("System error\n"));
+                       dbprintf(_("add_file: (Failed) System error\n"));
                        break;
+
                    case  0:
-                       printf("Added dir %s at date %s\n",
-                              ditem_path, lditem.date);
-                       dbprintf(("add_file: (Successful) Added dir %s at date %s\n",
-                                 ditem_path,lditem.date));
+                       quoted = quote_string(lditem.path);
+                       g_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;
                    }
@@ -647,157 +1025,243 @@ char *regex;
                if(!server_happy()) {
                    puts(reply_line());
                } else if(err) {
-                   puts(err);
-                   puts(cmd);
+                   if (*err)
+                       puts(err);
+                   if (cmd)
+                       puts(cmd);
                } else if(added == 0) {
-                   printf("dir %s already added\n", ditem_path);
-                   dbprintf(("add_file: dir %s already added\n", ditem_path));
+                   quoted = quote_string(ditem_path);
+                   g_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"));
+                   g_printf(_("System error\n"));
+                   dbprintf(_("add_file: (Failed) System error\n"));
                    break;
+
                case  0:
-                   printf("Added %s\n", ditem->path);
-                   dbprintf(("add_file: (Successful) Added %s\n",
-                             ditem->path));
+                   quoted = quote_string(ditem->path);
+                   g_printf(_("Added file %s\n"), quoted);
+                   dbprintf(_("add_file: (Successful) Added %s\n"), quoted);
+                   amfree(quoted);
                    break;
+
                case  1:
-                   printf("File %s already added\n", ditem->path);
-                   dbprintf(("add_file: file %s already added\n",
-                             ditem->path));
-                   break;
+                   quoted = quote_string(ditem->path);
+                   g_printf(_("File %s already added\n"), quoted);
+                   dbprintf(_("add_file: file %s already added\n"), quoted);
+                   amfree(quoted);
                }
            }
        }
     }
-    if (cmd != NULL)
-       amfree(cmd);
+
+    amfree(cmd);
     amfree(ditem_path);
     amfree(path_on_disk);
-    amfree(path_on_disk_slash);
+
+    amfree(lditem.path);
+    amfree(lditem.date);
+    amfree(lditem.tape);
 
     if(! found_one) {
-       printf("File %s doesn't exist in directory\n", path);
-       dbprintf(("add_file: (Failed) File %s doesn't exist in directory\n",
-                 path));
+       quoted = quote_string(path);
+       g_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(glob)
-char *glob;
+void
+delete_glob(
+    char *     glob)
 {
     char *regex;
     char *regex_path;
     char *s;
+    char *uqglob;
+    char *newglob;
+    char *dir;
+    char *sdir = NULL;
+    int   result = 1;
 
-    regex = glob_to_regex(glob);
-    dbprintf(("delete_glob (%s) -> %s\n", glob, regex));
-    if ((s = validate_regexp(regex)) != NULL) {
-       printf("\"%s\" is not a valid shell wildcard pattern: ", glob);
-       puts(s);
+    if (disk_path == NULL) {
+       g_printf(_("Must select directory before adding files\n"));
        return;
     }
-    /*
-     * 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);
-    amfree(regex);
-    regex_path[strlen(regex_path) - 1] = '\0';
-    strappend(regex_path, "[/]*$");
-    delete_file(glob, regex_path);
-    amfree(regex_path);
+
+    uqglob = unquote_string(glob);
+    newglob = file_of_path(uqglob, &dir);
+    if (dir) {
+       sdir = merge_path(mount_point, disk_path);
+       result = cd_glob(dir, 0);
+       amfree(dir);
+    }
+    if (result) {
+       regex = glob_to_regex(newglob);
+       dbprintf(_("delete_glob (%s) -> %s\n"), newglob, regex);
+       if ((s = validate_regexp(regex)) != NULL) {
+           g_printf(_("\"%s\" is not a valid shell wildcard pattern: "),
+                    newglob);
+           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);
+       }
+       if (sdir) {
+           set_directory(sdir, 0);
+       }
+       amfree(regex);
+    }
+    amfree(sdir);
+    amfree(uqglob);
+    amfree(newglob);
 }
 
-void delete_regex(regex)
-char *regex;
+void
+delete_regex(
+    char *     regex)
 {
     char *s;
+    char *dir;
+    char *sdir = NULL;
+    char *uqregex;
+    char *newregex;
+    int   result = 1;
 
-    if ((s = validate_regexp(regex)) != NULL) {
-       printf("\"%s\" is not a valid regular expression: ", regex);
-       puts(s);
+    if (disk_path == NULL) {
+       g_printf(_("Must select directory before adding files\n"));
        return;
     }
-    delete_file(regex, regex);
+
+    uqregex = unquote_string(regex);
+    newregex = file_of_path(uqregex, &dir);
+    if (dir) {
+       sdir = merge_path(mount_point, disk_path);
+       result = cd_regex(dir, 0);
+       amfree(dir);
+    }
+
+    if (result == 1) {
+       if ((s = validate_regexp(newregex)) != NULL) {
+           g_printf(_("\"%s\" is not a valid regular expression: "), newregex);
+           puts(s);
+       } else {
+           delete_file(newregex, regex);
+       }
+       if (sdir) {
+           set_directory(sdir, 0);
+       }
+    }
+    amfree(sdir);
+    amfree(uqregex);
+    amfree(newregex);
 }
 
-void delete_file(path, regex)
-char *path;
-char *regex;
+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;
-    int j;
-    char *date, *date_undo, date_undo_ch = '\0';
+    ssize_t j;
+    char *date;
     char *tape, *tape_undo, tape_undo_ch = '\0';
-    char *dir, *dir_undo, dir_undo_ch = '\0';
-    int  level, fileno;
+    char *dir_undo, dir_undo_ch = '\0';
+    int  level = 0;
+    off_t fileno;
     char *ditem_path = NULL;
+    char *qditem_path;
     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");
+       g_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));
-    /* remove "/" at the end of the path */
-    j = strlen(regex)-1;
-    while(j >= 0 && regex[j] == '/') regex[j--] = '\0';
+    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)
-       path_on_disk = stralloc2("/", regex);
-    else {
-       char *clean_disk_path = clean_regex(disk_path);
+    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, 0);
        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));
+    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))
     {
-       dbprintf(("delete_file: Pondering ditem->path=\"%s\"\n", ditem->path));
-       if (match(path_on_disk, ditem->path)
-           || match(path_on_disk_slash, ditem->path))
+       quoted = quote_string(ditem->path);
+       dbprintf(_("delete_file: Pondering ditem->path=%s\n"), quoted);
+       amfree(quoted);
+       if (match(path_on_disk, ditem->path))
        {
            found_one = 1;
-           j = strlen(ditem->path);
+           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);
+               qditem_path = quote_string(ditem_path);
+               cmd = newstralloc2(cmd, "ORLD ", qditem_path);
+               amfree(qditem_path);
                if(send_command(cmd) == -1) {
                    amfree(cmd);
                    amfree(ditem_path);
                    amfree(path_on_disk);
-                   amfree(path_on_disk_slash);
                    exit(1);
                }
                amfree(cmd);
@@ -805,36 +1269,33 @@ char *regex;
                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);
+                   g_printf("%s\n", l);
                    return;
                }
                deleted=0;
                 lditem.path = newstralloc(lditem.path, ditem->path);
                amfree(cmd);
-               date_undo = tape_undo = dir_undo = NULL;
+               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;
-                           date_undo = tape_undo = dir_undo = NULL;
+                           tape_undo = dir_undo = NULL;
                            cmd = stralloc(l);  /* save for the error report */
                        }
                        continue;       /* throw the rest of the lines away */
@@ -844,35 +1305,33 @@ char *regex;
                        puts(l);
                        continue;
                    }
-#define sc "201-"
-                   if(strncmp(l, sc, sizeof(sc)-1) != 0) {
-                       err = "bad reply: not 201-";
+
+                   s = l;
+                   if(strncmp_const_skip(l, "201-", s, ch) != 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";
+                       err = _("bad reply: missing date field");
                        continue;
                    }
                    date = s - 1;
                    skip_non_whitespace(s, ch);
-                   date_undo = s - 1;
-                   date_undo_ch = *date_undo;
-                   *date_undo = '\0';
+                   *(s - 1) = '\0';
 
                    skip_whitespace(s, ch);
                    if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
-                       err = "bad reply: cannot parse level field";
+                       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";
+                       err = _("bad reply: missing tape field");
                        continue;
                    }
                    tape = s - 1;
@@ -882,20 +1341,22 @@ char *regex;
                    *tape_undo = '\0';
 
                    if(am_has_feature(indexsrv_features, fe_amindexd_fileno_in_ORLD)) {
+                       long long fileno_ = (long long)0;
                        skip_whitespace(s, ch);
-                       if(ch == '\0' || sscanf(s - 1, "%d", &fileno) != 1) {
-                           err = "bad reply: cannot parse fileno field";
+                       if(ch == '\0' ||
+                          sscanf(s - 1, "%lld", &fileno_) != 1) {
+                           err = _("bad reply: cannot parse fileno field");
                            continue;
                        }
+                       fileno = (off_t)fileno_;
                        skip_integer(s, ch);
                    }
 
                    skip_whitespace(s, ch);
                    if(ch == '\0') {
-                       err = "bad reply: missing directory field";
+                       err = _("bad reply: missing directory field");
                        continue;
                    }
-                   dir = s - 1;
                    skip_non_whitespace(s, ch);
                    dir_undo = s - 1;
                    dir_undo_ch = *dir_undo;
@@ -906,13 +1367,13 @@ char *regex;
                     lditem.tape = newstralloc(lditem.tape, tape);
                    switch(delete_extract_item(&lditem)) {
                    case -1:
-                       printf("System error\n");
-                       dbprintf(("delete_file: (Failed) System error\n"));
+                       g_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));
+                       g_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:
@@ -922,34 +1383,34 @@ char *regex;
                if(!server_happy()) {
                    puts(reply_line());
                } else if(err) {
-                   if(*err) {
+                   if (*err)
                        puts(err);
-                   }
-                   puts(cmd);
+                   if (cmd)
+                       puts(cmd);
                } else if(deleted == 0) {
-                   printf("Warning - dir '%s' not on tape list\n",
+                   g_printf(_("Warning - dir '%s' not on tape list\n"),
                           ditem_path);
-                   dbprintf(("delete_file: 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"));
+                   g_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));
+                   g_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",
+                   g_printf(_("Warning - file '%s' not on tape list\n"),
                           ditem->path);
-                   dbprintf(("delete_file: file '%s' not on tape list\n",
-                             ditem->path));
+                   dbprintf(_("delete_file: file '%s' not on tape list\n"),
+                             ditem->path);
                    break;
                }
            }
@@ -958,25 +1419,26 @@ char *regex;
     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));
+       g_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(file)
-char *file;
+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)
     {
@@ -991,39 +1453,64 @@ char *file;
        pager_command = stralloc2(pager, " ; /bin/cat > /dev/null");
        if ((fp = popen(pager_command, "w")) == NULL)
        {
-           printf("Warning - can't pipe through %s\n", pager);
+           g_printf(_("Warning - can't pipe through %s\n"), pager);
            fp = stdout;
        }
        amfree(pager_command);
     }
     else
     {
-       if ((fp = fopen(file, "w")) == NULL)
+       uqfile = unquote_string(file);
+       if ((fp = fopen(uqfile, "w")) == NULL)
        {
-           printf("Can't open file '%s' to print extract list into\n", file);
+           g_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",
+       g_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);
+           g_fprintf(fp, "\t%s\n", that->path);
     }
 
     if (file == NULL) {
        apclose(fp);
     } else {
-       printf("Extract list written to file %s\n", file);
+       g_printf(_("Extract list written to file %s\n"), file);
        afclose(fp);
     }
 }
 
 
+static int
+is_empty_dir(
+    char *fname)
+{
+    DIR *dir;
+    struct dirent *entry;
+    int gotentry;
+
+    if((dir = opendir(fname)) == NULL)
+        return 1;
+
+    gotentry = 0;
+    while(!gotentry && (entry = readdir(dir)) != NULL) {
+        gotentry = !is_dot_or_dotdot(entry->d_name);
+    }
+
+    closedir(dir);
+    return !gotentry;
+
+}
+
 /* returns 0 if extract list empty and 1 if it isn't */
-int is_extract_list_nonempty P((void))
+int
+is_extract_list_nonempty(void)
 {
     return (extract_list != NULL);
 }
@@ -1031,30 +1518,31 @@ int is_extract_list_nonempty P((void))
 
 /* prints continue prompt and waits for response,
    returns 0 if don't, non-0 if do */
-static int okay_to_continue(allow_tape, allow_skip, allow_retry)
-    int allow_tape;
-    int allow_skip;
-    int allow_retry;
+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;
+    int get_device;
 
-    get_tape = 0;
+    get_device = 0;
     while (ret < 0) {
-       if (get_tape) {
-           prompt = "New tape device [?]: ";
+       if (get_device) {
+           prompt = _("New device name [?]: ");
        } else if (allow_tape && allow_skip) {
-           prompt = "Continue [?/Y/n/s/t]? ";
+           prompt = _("Continue [?/Y/n/s/d]? ");
        } else if (allow_tape && !allow_skip) {
-           prompt = "Continue [?/Y/n/t]? ";
+           prompt = _("Continue [?/Y/n/d]? ");
        } else if (allow_retry) {
-           prompt = "Continue [?/Y/n/r]? ";
+           prompt = _("Continue [?/Y/n/r]? ");
        } else {
-           prompt = "Continue [?/Y/n]? ";
+           prompt = _("Continue [?/Y/n]? ");
        }
        fputs(prompt, stdout);
        fflush(stdout); fflush(stderr);
@@ -1062,38 +1550,53 @@ static int okay_to_continue(allow_tape, allow_skip, allow_retry)
        if ((line = agets(stdin)) == NULL) {
            putchar('\n');
            clearerr(stdin);
-           if (get_tape) {
-               get_tape = 0;
+           if (get_device) {
+               get_device = 0;
                continue;
            }
            ret = 0;
            break;
        }
+       dbprintf("User prompt: '%s'; response: '%s'\n", prompt, line);
+
        s = line;
-       while ((ch = *s++) != '\0' && isspace(ch)) {}
+       while ((ch = *s++) != '\0' && g_ascii_isspace(ch)) {
+           (void)ch;   /* Quiet empty loop compiler warning */
+       }
        if (ch == '?') {
-           if (get_tape) {
-               printf("Enter a new device ([host:]device) or \"default\"\n");
+           if (get_device) {
+               g_printf(_("Enter a new device name or \"default\"\n"));
            } else {
-               printf("Enter \"y\"es to continue, \"n\"o to stop");
+               g_printf(_("Enter \"y\"es to continue, \"n\"o to stop"));
                if(allow_skip) {
-                   printf(", \"s\"kip this tape");
+                   g_printf(_(", \"s\"kip this tape"));
                }
                if(allow_retry) {
-                   printf(" or \"r\"etry this tape");
+                   g_printf(_(" or \"r\"etry this tape"));
                }
                if (allow_tape) {
-                   printf(" or \"t\"ape to change tape drives");
+                   g_printf(_(" or \"d\" to change to a new device"));
                }
                putchar('\n');
            }
-       } else if (get_tape) {
-           set_tape(s - 1);
-           get_tape = 0;
+       } else if (get_device) {
+           char *tmp = stralloc(tape_server_name);
+
+           if (strncmp_const(s - 1, "default") == 0) {
+               set_device(tmp, NULL); /* default device, existing host */
+           } else if (s[-1] != '\0') {
+               set_device(tmp, s - 1); /* specified device, existing host */
+           } else {
+               g_printf(_("No change.\n"));
+           }
+
+           amfree(tmp);
+
+           get_device = 0;
        } else if (ch == '\0' || ch == 'Y' || ch == 'y') {
            ret = 1;
-       } else if (allow_tape && (ch == 'T' || ch == 't')) {
-           get_tape = 1;
+       } else if (allow_tape && (ch == 'D' || ch == 'd' || ch == 'T' || ch == 't')) {
+           get_device = 1; /* ('T' and 't' are for backward-compatibility) */
        } else if (ch == 'N' || ch == 'n') {
            ret = 0;
        } else if (allow_retry && (ch == 'R' || ch == 'r')) {
@@ -1102,20 +1605,25 @@ static int okay_to_continue(allow_tape, allow_skip, allow_retry)
            ret = SKIP_TAPE;
        }
     }
+    /*@ignore@*/
     amfree(line);
+    /*@end@*/
     return ret;
 }
 
-static void send_to_tape_server(tss, cmd)
-int tss;
-char *cmd;
+static void
+send_to_tape_server(
+    security_stream_t *        stream,
+    char *             cmd)
 {
     char *msg = stralloc2(cmd, "\r\n");
 
-    if (fullwrite(tss, msg, strlen(msg)) < 0)
+    g_debug("send_to_tape_server: %s\n", cmd);
+    if (security_stream_write(stream, msg, strlen(msg)) < 0)
     {
-       error("Error writing to tape server");
+       error(_("Error writing to tape server"));
        exit(101);
+       /*NOTREACHED*/
     }
     amfree(msg);
 }
@@ -1124,105 +1632,42 @@ char *cmd;
 /* 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(label, fsf)
-char *label;
-int fsf;
+static int
+extract_files_setup(
+    char *     label,
+    off_t      fsf)
 {
-    struct servent *sp;
-    int 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;
+    char *req;
+    int response_error;
 
-    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,
-                                                 ntohs(sp->s_port),
-                                                 -1,
-                                                 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: %d\n", 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;
-       }
+    amidxtaped_secdrv = security_getdriver(authopt);
+    if (amidxtaped_secdrv == NULL) {
+       error(_("no '%s' security driver available for host '%s'"),
+             authopt, tape_server_name);
     }
 
-    /* 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 assume that amidxtaped support fe_amidxtaped_options_features */
+    /*                               and fe_amidxtaped_options_auth     */
+    /* We should send a noop to really know                             */
+    req = vstralloc("SERVICE amidxtaped\n",
+                   "OPTIONS ", "features=", our_features_string, ";",
+                               "auth=", authopt, ";",
+                   "\n", NULL);
+    protocol_sendreq(tape_server_name, amidxtaped_secdrv,
+                    generic_client_get_security_conf, req, STARTUP_TIMEOUT,
+                    amidxtaped_response, &response_error);
+    amfree(req);
+    protocol_run();
+    if(response_error != 0) {
+       return -1;
     }
 
-    /* we want to force amrestore to only match dump_hostname exactly */
-    *(ch1++) = '$';
-
-    *ch1 = '\0';
+    disk_regex = make_exact_disk_expression(disk_name);
+    host_regex = make_exact_host_expression(dump_hostname);
 
     clean_datestamp = stralloc(dump_datestamp);
     for(ch=ch1=clean_datestamp;*ch1 != '\0';ch1++) {
@@ -1232,22 +1677,25 @@ int fsf;
        }
     }
     *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 */
+    if(am_has_feature(indexsrv_features, fe_amidxtaped_exchange_features)){
+       tt = newstralloc2(tt, "FEATURES=", our_features_string);
+       send_to_tape_server(amidxtaped_streams[CTLFD].fd, tt);
+       get_amidxtaped_line();
+       if(strncmp_const(amidxtaped_line,"FEATURES=") == 0) {
+           tapesrv_features = am_string_to_feature(amidxtaped_line+9);
+       } else {
+           g_fprintf(stderr, _("amrecover - expecting FEATURES line from amidxtaped\n"));
+           stop_amidxtaped();
+           amfree(disk_regex);
+           amfree(host_regex);
+           amfree(clean_datestamp);
+           return -1;
        }
-       
-       tapesrv_features = am_string_to_feature(buffer);
-       amfree(our_feature_string);
+    } else {
+       *tapesrv_features = *indexsrv_features;
     }
 
 
@@ -1258,30 +1706,30 @@ int fsf;
        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);
+           tt = newstralloc2(tt, "CONFIG=", get_config_name());
+           send_to_tape_server(amidxtaped_streams[CTLFD].fd, 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);
+           send_to_tape_server(amidxtaped_streams[CTLFD].fd, tt);
        }
        if(am_has_feature(indexsrv_features, fe_amidxtaped_fsf)) {
            char v_fsf[100];
-           snprintf(v_fsf, 99, "%d", fsf);
+           g_snprintf(v_fsf, 99, "%lld", (long long)fsf);
            tt = newstralloc2(tt, "FSF=",v_fsf);
-           send_to_tape_server(tape_control_sock, tt);
+           send_to_tape_server(amidxtaped_streams[CTLFD].fd, tt);
        }
-       send_to_tape_server(tape_control_sock, "HEADER");
+       send_to_tape_server(amidxtaped_streams[CTLFD].fd, "HEADER");
        tt = newstralloc2(tt, "DEVICE=", dump_device_name);
-       send_to_tape_server(tape_control_sock, tt);
+       send_to_tape_server(amidxtaped_streams[CTLFD].fd, tt);
        tt = newstralloc2(tt, "HOST=", host_regex);
-       send_to_tape_server(tape_control_sock, tt);
+       send_to_tape_server(amidxtaped_streams[CTLFD].fd, tt);
        tt = newstralloc2(tt, "DISK=", disk_regex);
-       send_to_tape_server(tape_control_sock, tt);
+       send_to_tape_server(amidxtaped_streams[CTLFD].fd, tt);
        tt = newstralloc2(tt, "DATESTAMP=", clean_datestamp);
-       send_to_tape_server(tape_control_sock, tt);
-       send_to_tape_server(tape_control_sock, "END");
+       send_to_tape_server(amidxtaped_streams[CTLFD].fd, tt);
+       send_to_tape_server(amidxtaped_streams[CTLFD].fd, "END");
        amfree(tt);
     }
     else if(am_has_feature(indexsrv_features, fe_amidxtaped_nargs)) {
@@ -1294,121 +1742,82 @@ int fsf;
         *   "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];
-       int data_port = -1;
-        int 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 %d\n", &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,
-                                                 -1,
-                                                 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);
+       send_to_tape_server(amidxtaped_streams[CTLFD].fd, "6");
+       send_to_tape_server(amidxtaped_streams[CTLFD].fd, "-h");
+       send_to_tape_server(amidxtaped_streams[CTLFD].fd, "-p");
+       send_to_tape_server(amidxtaped_streams[CTLFD].fd, dump_device_name);
+       send_to_tape_server(amidxtaped_streams[CTLFD].fd, host_regex);
+       send_to_tape_server(amidxtaped_streams[CTLFD].fd, disk_regex);
+       send_to_tape_server(amidxtaped_streams[CTLFD].fd, clean_datestamp);
+
+       dbprintf(_("Started amidxtaped with arguments \"6 -h -p %s %s %s %s\"\n"),
+                 dump_device_name, host_regex, disk_regex, clean_datestamp);
     }
 
     amfree(disk_regex);
     amfree(host_regex);
     amfree(clean_datestamp);
 
-    return tape_control_sock;
+    return 0;
 }
 
 
-void read_file_header(buffer, file, buflen, tapedev)
-char *buffer;
-dumpfile_t *file;
-size_t buflen;
-int tapedev;
 /*
  * 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",
+       error(_("error reading header (%s), check amidxtaped.*.debug on server"),
              strerror(errno));
-       /* NOTREACHED */
+       /*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");
+       g_fprintf(stderr, plural(_("%s: short block %d byte\n"),
+                              _("%s: short block %d bytes\n"), bytes_read),
+               get_pname(), (int)bytes_read);
        print_header(stdout, file);
-       error("Can't read file header");
-       /* NOTREACHED */
+       error(_("Can't read file header"));
+       /*NOTREACHED*/
     }
 
     /* bytes_read == buflen */
-    parse_file_header(buffer, file, bytes_read);
+    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(in_fd, elist)
-    int in_fd;
-    EXTRACT_LIST *elist;
+enum dumptypes {
+       IS_UNKNOWN,
+       IS_DUMP,
+       IS_GNUTAR,
+       IS_TAR,
+       IS_SAMBA,
+       IS_SAMBA_TAR,
+       IS_APPLICATION_API
+};
+
+static void
+extract_files_child(
+    ctl_data_t         *ctl_data)
 {
     int save_errno;
-    int extra_params = 0;
-    int i,j=0;
-    char **restore_args = NULL;
+    int   i;
+    guint j;
+    GPtrArray *argv_ptr = g_ptr_array_new();
     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;
+    guint passwd_field = 999999999;
 #ifdef SAMBA_CLIENT
     char *domain = NULL, *smbpass = NULL;
 #endif
@@ -1417,37 +1826,35 @@ static void extract_files_child(in_fd, elist)
     /* never returns */
 
     /* make in_fd be our stdin */
-    if (dup2(in_fd, STDIN_FILENO) == -1)
+    if (dup2(ctl_data->child_pipe[0], STDIN_FILENO) == -1)
     {
-       error("dup2 failed in extract_files_child: %s", strerror(errno));
-       /* NOTREACHED */
+       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(ctl_data->file.type != F_DUMPFILE) {
+       dump_dumpfile_t(&ctl_data->file);
+       error(_("bad header"));
+       /*NOTREACHED*/
     }
 
-    if (file.program != NULL) {
+    if (ctl_data->file.program != NULL) {
+       if (strcmp(ctl_data->file.program, "APPLICATION") == 0)
+           dumptype = IS_APPLICATION_API;
 #ifdef GNUTAR
-       if (strcmp(file.program, GNUTAR) == 0)
+       if (strcmp(ctl_data->file.program, GNUTAR) == 0)
            dumptype = IS_GNUTAR;
 #endif
 
        if (dumptype == IS_UNKNOWN) {
-           len_program = strlen(file.program);
+           len_program = strlen(ctl_data->file.program);
            if(len_program >= 3 &&
-              strcmp(&file.program[len_program-3],"tar") == 0)
+              strcmp(&ctl_data->file.program[len_program-3],"tar") == 0)
                dumptype = IS_TAR;
        }
 
 #ifdef SAMBA_CLIENT
-       if (dumptype == IS_UNKNOWN && strcmp(file.program, SAMBA_CLIENT) ==0) {
+       if (dumptype == IS_UNKNOWN && strcmp(ctl_data->file.program, SAMBA_CLIENT) ==0) {
            if (samba_extract_method == SAMBA_TAR)
              dumptype = IS_SAMBA_TAR;
            else
@@ -1457,135 +1864,151 @@ static void extract_files_child(in_fd, elist)
     }
 
     /* 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((extra_params + files_off_tape + 1)
-                                 * sizeof(char *));
+    files_off_tape = length_of_tape_list(ctl_data->elist);
     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;
+       g_ptr_array_add(argv_ptr, stralloc("smbclient"));
+       smbpass = findpass(ctl_data->file.disk, &domain);
+       if (smbpass) {
+           g_ptr_array_add(argv_ptr, stralloc(ctl_data->file.disk));
+           g_ptr_array_add(argv_ptr, stralloc("-U"));
+           passwd_field = argv_ptr->len;
+           g_ptr_array_add(argv_ptr, stralloc(smbpass));
+           if (domain) {
+               g_ptr_array_add(argv_ptr, stralloc("-W"));
+               g_ptr_array_add(argv_ptr, stralloc(domain));
+           }
+       }
+       g_ptr_array_add(argv_ptr, stralloc("-d0"));
+       g_ptr_array_add(argv_ptr, stralloc("-Tx"));
+       g_ptr_array_add(argv_ptr, 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 */
+       g_ptr_array_add(argv_ptr, stralloc("tar"));
+       /* ignore trailing zero blocks on input (this was the default until tar-1.21) */
+       g_ptr_array_add(argv_ptr, stralloc("--ignore-zeros"));
+       g_ptr_array_add(argv_ptr, stralloc("--numeric-owner"));
+       g_ptr_array_add(argv_ptr, stralloc("-xpGvf"));
+       g_ptr_array_add(argv_ptr, 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 */
+       g_ptr_array_add(argv_ptr, stralloc("tar"));
+       g_ptr_array_add(argv_ptr, stralloc("-xpvf"));
+       g_ptr_array_add(argv_ptr, stralloc("-"));       /* data on stdin */
        break;
     case IS_UNKNOWN:
     case IS_DUMP:
-        restore_args[j++] = stralloc("restore");
+       g_ptr_array_add(argv_ptr, stralloc("restore"));
 #ifdef AIX_BACKUP
         restore_args[j++] = stralloc("-xB");
+       g_ptr_array_add(argv_ptr, stralloc("-xB"));
 #else
 #if defined(XFSDUMP)
-       if (strcmp(file.program, XFSDUMP) == 0) {
-            restore_args[j++] = stralloc("-v");
-            restore_args[j++] = stralloc("silent");
+       if (strcmp(ctl_data->file.program, XFSDUMP) == 0) {
+           g_ptr_array_add(argv_ptr, stralloc("-v"));
+           g_ptr_array_add(argv_ptr, 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 */
+       if (strcmp(ctl_data->file.program, VDUMP) == 0) {
+           g_ptr_array_add(argv_ptr, stralloc("xf"));
+           g_ptr_array_add(argv_ptr, 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 */
+       g_ptr_array_add(argv_ptr, stralloc("xbf"));
+       g_ptr_array_add(argv_ptr, stralloc("2")); /* read in units of 1K */
+       g_ptr_array_add(argv_ptr, stralloc("-"));       /* data on stdin */
        }
 #endif
+       break;
+    case IS_APPLICATION_API:
+       g_ptr_array_add(argv_ptr, stralloc(ctl_data->file.application));
+       g_ptr_array_add(argv_ptr, stralloc("restore"));
+       g_ptr_array_add(argv_ptr, stralloc("--config"));
+       g_ptr_array_add(argv_ptr, stralloc(get_config_name()));
+       g_ptr_array_add(argv_ptr, stralloc("--disk"));
+       g_ptr_array_add(argv_ptr, stralloc(ctl_data->file.disk));
+       if (dump_dle && dump_dle->device) {
+           g_ptr_array_add(argv_ptr, stralloc("--device"));
+           g_ptr_array_add(argv_ptr, stralloc(dump_dle->device));
+       }
+       if (ctl_data->data_path == DATA_PATH_DIRECTTCP) {
+           g_ptr_array_add(argv_ptr, stralloc("--data-path"));
+           g_ptr_array_add(argv_ptr, stralloc("DIRECTTCP"));
+           g_ptr_array_add(argv_ptr, stralloc("--direct-tcp"));
+           g_ptr_array_add(argv_ptr, stralloc(ctl_data->addrs));
+       }
+       if (ctl_data->bsu && ctl_data->bsu->smb_recover_mode &&
+           samba_extract_method == SAMBA_SMBCLIENT){
+           g_ptr_array_add(argv_ptr, stralloc("--recover-mode"));
+           g_ptr_array_add(argv_ptr, stralloc("smb"));
+       }
+       g_ptr_array_add(argv_ptr, stralloc("--level"));
+       g_ptr_array_add(argv_ptr, g_strdup_printf("%d", ctl_data->elist->level));
+       if (dump_dle) {
+           GSList   *scriptlist;
+           script_t *script;
+
+           merge_properties(dump_dle->application_property, proplist);
+           application_property_add_to_argv(argv_ptr, dump_dle, NULL,
+                                            tapesrv_features);
+           for (scriptlist = dump_dle->scriptlist; scriptlist != NULL;
+                scriptlist = scriptlist->next) {
+               script = (script_t *)scriptlist->data;
+               if (script->result && script->result->proplist) {
+                   property_add_to_argv(argv_ptr, script->result->proplist);
+               }
+           }
+
+       } else if (proplist) {
+           property_add_to_argv(argv_ptr, proplist);
+       }
+       break;
     }
-  
-    for (i = 0, fn = elist->files; i < files_off_tape; i++, fn = fn->next)
+
+    for (i = 0, fn = ctl_data->elist->files; i < files_off_tape;
+                                            i++, fn = fn->next)
     {
        switch (dumptype) {
+       case IS_APPLICATION_API:
        case IS_TAR:
        case IS_GNUTAR:
        case IS_SAMBA_TAR:
        case IS_SAMBA:
            if (strcmp(fn->path, "/") == 0)
-               restore_args[j++] = stralloc(".");
+               g_ptr_array_add(argv_ptr, stralloc("."));
            else
-               restore_args[j++] = stralloc2(".", fn->path);
+               g_ptr_array_add(argv_ptr, stralloc2(".", fn->path));
            break;
        case IS_UNKNOWN:
        case IS_DUMP:
 #if defined(XFSDUMP)
-           if (strcmp(file.program, XFSDUMP) == 0) {
+           if (strcmp(ctl_data->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);
+               g_ptr_array_add(argv_ptr, stralloc("-s"));
+               g_ptr_array_add(argv_ptr, stralloc(fn->path + 1));
            } else
 #endif
            {
-           restore_args[j++] = stralloc(fn->path);
+           g_ptr_array_add(argv_ptr, stralloc(fn->path));
            }
+           break;
        }
     }
 #if defined(XFSDUMP)
-    if (strcmp(file.program, XFSDUMP) == 0) {
-       restore_args[j++] = stralloc("-");
-       restore_args[j++] = stralloc(".");
+    if (strcmp(ctl_data->file.program, XFSDUMP) == 0) {
+       g_ptr_array_add(argv_ptr, stralloc("-"));
+       g_ptr_array_add(argv_ptr, stralloc("."));
     }
 #endif
-    restore_args[j] = NULL;
+    g_ptr_array_add(argv_ptr, NULL);
 
     switch (dumptype) {
     case IS_SAMBA:
@@ -1599,7 +2022,7 @@ static void extract_files_child(in_fd, elist)
     case IS_GNUTAR:
     case IS_SAMBA_TAR:
 #ifndef GNUTAR
-       fprintf(stderr, "warning: GNUTAR program not available.\n");
+       g_fprintf(stderr, _("warning: GNUTAR program not available.\n"));
        cmd = stralloc("tar");
 #else
        cmd = stralloc(GNUTAR);
@@ -1609,49 +2032,51 @@ static void extract_files_child(in_fd, elist)
     case IS_DUMP:
        cmd = NULL;
 #if defined(DUMP)
-       if (strcmp(file.program, DUMP) == 0) {
+       if (strcmp(ctl_data->file.program, DUMP) == 0) {
            cmd = stralloc(RESTORE);
        }
 #endif
 #if defined(VDUMP)
-       if (strcmp(file.program, VDUMP) == 0) {
+       if (strcmp(ctl_data->file.program, VDUMP) == 0) {
            cmd = stralloc(VRESTORE);
        }
 #endif
 #if defined(VXDUMP)
-       if (strcmp(file.program, VXDUMP) == 0) {
+       if (strcmp(ctl_data->file.program, VXDUMP) == 0) {
            cmd = stralloc(VXRESTORE);
        }
 #endif
 #if defined(XFSDUMP)
-       if (strcmp(file.program, XFSDUMP) == 0) {
+       if (strcmp(ctl_data->file.program, XFSDUMP) == 0) {
            cmd = stralloc(XFSRESTORE);
        }
 #endif
        if (cmd == NULL) {
-           fprintf(stderr, "warning: restore program for %s not available.\n",
-                   file.program);
+           g_fprintf(stderr, _("warning: restore program for %s not available.\n"),
+                   ctl_data->file.program);
            cmd = stralloc("restore");
        }
+       break;
+    case IS_APPLICATION_API:
+       cmd = vstralloc(APPLICATION_DIR, "/", ctl_data->file.application, NULL);
+       break;
     }
     if (cmd) {
-        dbprintf(("Exec'ing %s with arguments:\n", cmd));
-       for (i = 0; i < j; i++) {
-           if( i == passwd_field)
-               dbprintf(("\tXXXXX\n"));
+        dbprintf(_("Exec'ing %s with arguments:\n"), cmd);
+       for (j = 0; j < argv_ptr->len - 1; j++) {
+           if (j == passwd_field)
+               dbprintf("\tXXXXX\n");
            else
-               dbprintf(("\t%s\n", restore_args[i]));
+               dbprintf(_("\t%s\n"), (char *)g_ptr_array_index(argv_ptr, j));
        }
-        (void)execv(cmd, restore_args);
+       safe_fd(-1, 0);
+        (void)execv(cmd, (char **)argv_ptr->pdata);
        /* only get here if exec failed */
        save_errno = errno;
-       for (i = 0; i < j; i++) {
-           amfree(restore_args[i]);
-       }
-       amfree(restore_args);
+       g_ptr_array_free_full(argv_ptr);
        errno = save_errno;
-        perror("amrecover couldn't exec");
-        fprintf(stderr, " problem executing %s\n", cmd);
+        perror(_("amrecover couldn't exec"));
+        g_fprintf(stderr, _(" problem executing %s\n"), cmd);
        amfree(cmd);
     }
     exit(1);
@@ -1663,217 +2088,135 @@ static void extract_files_child(in_fd, elist)
  * 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(ctl_fd, data_fd, elist)
-    int ctl_fd, data_fd;
-    EXTRACT_LIST *elist;
+int
+writer_intermediary(
+    EXTRACT_LIST *     elist)
 {
-    int child_pipe[2];
-    pid_t pid;
-    char buffer[DISK_BLOCK_BYTES];
-    ssize_t s;
-    ssize_t bytes_read;
-    amwait_t extractor_status;
-    int max_fd, nfound;
-    fd_set 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, (SELECT_ARG_TYPE *)(&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 %s\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;
+    ctl_data_t ctl_data;
+    amwait_t   extractor_status;
+
+    ctl_data.header_done   = 0;
+    ctl_data.child_pipe[0] = -1;
+    ctl_data.child_pipe[1] = -1;
+    ctl_data.pid           = -1;
+    ctl_data.elist         = elist;
+    fh_init(&ctl_data.file);
+    ctl_data.data_path     = DATA_PATH_AMANDA;
+    ctl_data.addrs         = NULL;
+    ctl_data.bsu           = NULL;
+    ctl_data.bytes_read    = 0;
+
+    security_stream_read(amidxtaped_streams[DATAFD].fd,
+                        read_amidxtaped_data, &ctl_data);
+
+    while(get_amidxtaped_line() >= 0) {
+       char desired_tape[MAX_TAPE_LABEL_BUF];
+       g_debug("get amidxtaped line: %s", amidxtaped_line);
+
+       /* if prompted for a tape, relay said prompt to the user */
+       if(sscanf(amidxtaped_line, "FEEDME %132s\n", desired_tape) == 1) {
+           int done;
+           g_printf(_("Load tape %s now\n"), desired_tape);
+           dbprintf(_("Requesting tape %s from user\n"), desired_tape);
+           done = okay_to_continue(am_has_feature(indexsrv_features,
+                                                  fe_amrecover_feedme_tape),
+                                   0, 0);
+           if (done == 1) {
+               if (am_has_feature(indexsrv_features,
+                                  fe_amrecover_feedme_tape)) {
+                   char *reply = stralloc2("TAPE ", tape_device_name);
+                   send_to_tape_server(amidxtaped_streams[CTLFD].fd, reply);
+                   amfree(reply);
+               } else {
+                   send_to_tape_server(amidxtaped_streams[CTLFD].fd, "OK");
                }
+           } else {
+               send_to_tape_server(amidxtaped_streams[CTLFD].fd, "ERROR");
+               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((s = fullwrite(child_pipe[1], buffer, 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;
+       } else if (strncmp_const(amidxtaped_line, "USE-DATAPATH ") == 0) {
+           if (strncmp_const(amidxtaped_line+13, "AMANDA") == 0) {
+               ctl_data.data_path = DATA_PATH_AMANDA;
+               g_debug("Using AMANDA data-path");
+           } else if (strncmp_const(amidxtaped_line+13, "DIRECT-TCP") == 0) {
+               ctl_data.data_path = DATA_PATH_DIRECTTCP;
+               ctl_data.addrs = stralloc(amidxtaped_line+24);
+               g_debug("Using DIRECT-TCP data-path with %s", ctl_data.addrs);
            }
+           start_processing_data(&ctl_data);
+       } else if(strncmp_const(amidxtaped_line, "MESSAGE ") == 0) {
+           g_printf("%s\n",&amidxtaped_line[8]);
+       } else {
+           g_fprintf(stderr, _("Strange message from tape server: %s"),
+                   amidxtaped_line);
+           break;
        }
-    } while(FD_ISSET(ctl_fd, &readset) || FD_ISSET(data_fd, &readset));
+    }
 
-    aclose(child_pipe[1]);
+    /* CTL might be close before DATA */
+    event_loop(0);
+    dumpfile_free_data(&ctl_data.file);
+    amfree(ctl_data.addrs);
+    amfree(ctl_data.bsu);
+    if (ctl_data.child_pipe[1] != -1)
+       aclose(ctl_data.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 */
+    if (ctl_data.header_done == 0) {
+       g_printf(_("Got no header and data from server, check in amidxtaped.*.debug and amandad.*.debug files on server\n"));
     }
 
-    exit(0);
+    if (ctl_data.pid != -1) {
+       waitpid(ctl_data.pid, &extractor_status, 0);
+       if(WEXITSTATUS(extractor_status) != 0){
+           int ret = WEXITSTATUS(extractor_status);
+            if(ret == 255) ret = -1;
+           g_printf(_("Extractor child exited with status %d\n"), ret);
+           return -1;
+       }
+    }
+    g_debug("bytes read: %jd", (intmax_t)ctl_data.bytes_read);
+    return(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 P((void))
+/*
+ * 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;
+    tapelist_t *tlist = NULL, *a_tlist;
+    g_option_t g_options;
+    levellist_t all_level = NULL;
+    int last_level;
 
     if (!is_extract_list_nonempty())
     {
-       printf("Extract list empty - No files to extract!\n");
+       g_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);
@@ -1886,7 +2229,7 @@ void extract_files P((void))
        l = reply_line();
        if (!server_happy())
        {
-           printf("%s\n", l);
+           g_printf("%s\n", l);
            exit(1);
        }
        /* skip reply number */
@@ -1895,72 +2238,82 @@ void extract_files P((void))
 
     if (strcmp(tape_device_name, "/dev/null") == 0)
     {
-       printf("amrecover: warning: using %s as the tape device will not work\n",
+       g_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))
+    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",
+               g_printf(_("\nExtracting files using tape drive %s on host %s.\n"),
                        tape_device_name, tape_server_name);
-               printf("The following tapes are needed:");
+               g_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);
+               g_printf("                               ");
+           tlist = unmarshal_tapelist_str(elist->tape);
+           for(a_tlist = tlist ; a_tlist != NULL; a_tlist = a_tlist->next)
+               g_printf(" %s", a_tlist->label);
+           g_printf("\n");
+           free_tapelist(tlist);
        }
+    }
     first=1;
-    for (elist = first_tape_list(); elist != NULL; elist = next_tape_list(elist))
+    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",
+               g_printf(_("\nExtracting files from holding disk on host %s.\n"),
                        tape_server_name);
-               printf("The following files are needed:");
+               g_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");
-    getcwd(buf, sizeof(buf));
-    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");
-
+               g_printf("                               ");
+           tlist = unmarshal_tapelist_str(elist->tape);
+           for(a_tlist = tlist; a_tlist != NULL; a_tlist = a_tlist->next)
+               g_printf(" %s", a_tlist->label);
+           g_printf("\n");
+           free_tapelist(tlist);
+       }
+    }
+    g_printf("\n");
+
+    g_options.config = get_config_name();
+    g_options.hostname = dump_hostname;
+    for (elist = first_tape_list(); elist != NULL;
+        elist = next_tape_list(elist)) {
+       level_t *level = g_new0(level_t, 1);
+       level->level = elist->level;
+       all_level = g_slist_append(all_level, level);
+    }
+    if (dump_dle) {
+       g_slist_free_full(dump_dle->levellist);
+       dump_dle->levellist = all_level;
+       run_client_scripts(EXECUTE_ON_PRE_RECOVER, &g_options, dump_dle,
+                          stderr);
+       dump_dle->levellist = NULL;
+    }
+    last_level = -1;
     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);
+           g_printf(_("Extracting from file "));
+           tlist = unmarshal_tapelist_str(dump_device_name);
+           for(a_tlist = tlist; a_tlist != NULL; a_tlist = a_tlist->next)
+               g_printf(" %s", a_tlist->label);
+           g_printf("\n");
+           free_tapelist(tlist);
        }
        else {
-           printf("Extracting files using tape drive %s on host %s.\n",
+           g_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);
+           tlist = unmarshal_tapelist_str(elist->tape);
+           g_printf(_("Load tape %s now\n"), tlist->label);
+           dbprintf(_("Requesting tape %s from user\n"), tlist->label);
+           free_tapelist(tlist);
            otc = okay_to_continue(1,1,0);
            if (otc == 0)
                return;
@@ -1972,67 +2325,524 @@ void extract_files P((void))
        }
        dump_datestamp = newstralloc(dump_datestamp, elist->date);
 
+       if (last_level != -1 && dump_dle) {
+           level_t *level;
+
+           level = g_new0(level_t, 1);
+           level->level = last_level;
+           dump_dle->levellist = g_slist_append(dump_dle->levellist, level);
+
+           level = g_new0(level_t, 1);
+           level->level = elist->level;
+           dump_dle->levellist = g_slist_append(dump_dle->levellist, level);
+           run_client_scripts(EXECUTE_ON_INTER_LEVEL_RECOVER, &g_options,
+                              dump_dle, stderr);
+           g_slist_free_full(dump_dle->levellist);
+           dump_dle->levellist = NULL;
+       }
+
        /* connect to the tape handler daemon on the tape drive server */
-       if ((tape_control_sock = extract_files_setup(elist->tape, elist->fileno)) == -1)
+       if ((extract_files_setup(elist->tape, elist->fileno)) == -1)
        {
-           fprintf(stderr, "amrecover - can't talk to tape server\n");
+           g_fprintf(stderr, _("amrecover - can't talk to tape server: %s\n"),
+                   errstr);
            return;
        }
+       if (dump_dle) {
+           level_t *level;
+
+           level = g_new0(level_t, 1);
+           level->level = elist->level;
+           dump_dle->levellist = g_slist_append(dump_dle->levellist, level);
+           run_client_scripts(EXECUTE_ON_PRE_LEVEL_RECOVER, &g_options,
+                              dump_dle, stderr);
+       }
+       last_level = elist->level;
 
-       /* 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*/
+       /* if the server have fe_amrecover_feedme_tape, it has asked for
+        * the tape itself, even if the restore didn't succeed, we should
+        * remove it.
+        */
+       if(writer_intermediary(elist) == 0 ||
+          am_has_feature(indexsrv_features, fe_amrecover_feedme_tape))
+           delete_tape_list(elist);    /* tape done so delete from list */
+
+       am_release_feature_set(tapesrv_features);
+       stop_amidxtaped();
+
+       if (dump_dle) {
+           run_client_scripts(EXECUTE_ON_POST_LEVEL_RECOVER, &g_options,
+                              dump_dle, stderr);
+           g_slist_free_full(dump_dle->levellist);
+           dump_dle->levellist = NULL;
        }
-       /* this is the parent */
-       if (pid == -1)
-       {
-           perror("extract_list - error forking child");
-           exit(1);
+    }
+    if (dump_dle) {
+       dump_dle->levellist = all_level;
+       run_client_scripts(EXECUTE_ON_POST_RECOVER, &g_options, dump_dle,
+                          stderr);
+       g_slist_free_full(dump_dle->levellist);
+       all_level = NULL;
+       dump_dle->levellist = NULL;
+    }
+}
+
+static void
+amidxtaped_response(
+    void *             datap,
+    pkt_t *            pkt,
+    security_handle_t *        sech)
+{
+    int ports[NSTREAMS], *response_error = datap, i;
+    char *p;
+    char *tok;
+    char *extra = NULL;
+
+    assert(response_error != NULL);
+    assert(sech != NULL);
+    memset(ports, -1, SIZEOF(ports));
+
+    if (pkt == NULL) {
+       errstr = newvstrallocf(errstr, _("[request failed: %s]"), security_geterror(sech));
+       *response_error = 1;
+       return;
+    }
+    security_close_connection(sech, dump_hostname);
+
+    if (pkt->type == P_NAK) {
+#if defined(PACKET_DEBUG)
+       g_fprintf(stderr, _("got nak response:\n----\n%s\n----\n\n"), pkt->body);
+#endif
+
+       tok = strtok(pkt->body, " ");
+       if (tok == NULL || strcmp(tok, "ERROR") != 0)
+           goto bad_nak;
+
+       tok = strtok(NULL, "\n");
+       if (tok != NULL) {
+           errstr = newvstralloc(errstr, "NAK: ", tok, NULL);
+           *response_error = 1;
+       } else {
+bad_nak:
+           errstr = newstralloc(errstr, "request NAK");
+           *response_error = 2;
        }
+       return;
+    }
 
-       /* store the child pid globally so that it can be killed on intr */
-       extract_restore_child_pid = pid;
+    if (pkt->type != P_REP) {
+       errstr = newvstrallocf(errstr, _("received strange packet type %s: %s"),
+                             pkt_type2str(pkt->type), pkt->body);
+       *response_error = 1;
+       return;
+    }
 
-       /* 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 defined(PACKET_DEBUG)
+    g_fprintf(stderr, _("got response:\n----\n%s\n----\n\n"), pkt->body);
+#endif
+
+    for(i = 0; i < NSTREAMS; i++) {
+        ports[i] = -1;
+        amidxtaped_streams[i].fd = NULL;
+    }
+
+    p = pkt->body;
+    while((tok = strtok(p, " \n")) != NULL) {
+       p = NULL;
+
+       /*
+        * Error response packets have "ERROR" followed by the error message
+        * followed by a newline.
+        */
+       if (strcmp(tok, "ERROR") == 0) {
+           tok = strtok(NULL, "\n");
+           if (tok == NULL)
+               tok = _("[bogus error packet]");
+           errstr = newstralloc(errstr, tok);
+           *response_error = 2;
+           return;
        }
 
-       if(tape_data_sock != -1) {
-           aclose(tape_data_sock);
+
+        /*
+         * Regular packets have CONNECT followed by three streams
+         */
+        if (strcmp(tok, "CONNECT") == 0) {
+
+           /*
+            * Parse the three stream specifiers out of the packet.
+            */
+           for (i = 0; i < NSTREAMS; i++) {
+               tok = strtok(NULL, " ");
+               if (tok == NULL || strcmp(tok, amidxtaped_streams[i].name) != 0) {
+                   extra = vstrallocf(_("CONNECT token is \"%s\": expected \"%s\""),
+                                     tok ? tok : "(null)",
+                                     amidxtaped_streams[i].name);
+                   goto parse_error;
+               }
+               tok = strtok(NULL, " \n");
+               if (tok == NULL || sscanf(tok, "%d", &ports[i]) != 1) {
+                   extra = vstrallocf(_("CONNECT %s token is \"%s\": expected a port number"),
+                                     amidxtaped_streams[i].name,
+                                     tok ? tok : "(null)");
+                   goto parse_error;
+               }
+           }
+           continue;
        }
 
-       if (pid == extract_restore_child_pid)
-       {
-           extract_restore_child_pid = -1;
+       /*
+        * OPTIONS [options string] '\n'
+        */
+       if (strcmp(tok, "OPTIONS") == 0) {
+           tok = strtok(NULL, "\n");
+           if (tok == NULL) {
+               extra = stralloc(_("OPTIONS token is missing"));
+               goto parse_error;
+           }
+/*
+           while((p = strchr(tok, ';')) != NULL) {
+               *p++ = '\0';
+               if(strncmp_const(tok, "features=") == 0) {
+                   tok += sizeof("features=") - 1;
+                   am_release_feature_set(their_features);
+                   if((their_features = am_string_to_feature(tok)) == NULL) {
+                       errstr = newvstralloc(errstr,
+                                             _("OPTIONS: bad features value: "),
+                                             tok,
+                                             NULL);
+                       goto parse_error;
+                   }
+               }
+               tok = p;
+           }
+*/
+           continue;
        }
-       else
-       {
-           fprintf(stderr, "extract list - unknown child terminated?\n");
-           exit(1);
+/*
+       extra = vstrallocf("next token is \"%s\": expected \"CONNECT\", \"ERROR\" or \"OPTIONS\""),
+                         tok ? tok : _("(null)"));
+       goto parse_error;
+*/
+    }
+
+    /*
+     * Connect the streams to their remote ports
+     */
+    for (i = 0; i < NSTREAMS; i++) {
+       if (ports[i] == -1)
+           continue;
+       amidxtaped_streams[i].fd = security_stream_client(sech, ports[i]);
+       dbprintf(_("amidxtaped_streams[%d].fd = %p\n"),i, amidxtaped_streams[i].fd);
+       if (amidxtaped_streams[i].fd == NULL) {
+           errstr = newvstrallocf(errstr,\
+                       _("[could not connect %s stream: %s]"),
+                       amidxtaped_streams[i].name,
+                       security_geterror(sech));
+           goto connect_error;
        }
-       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;
-           else if(otc == 1) {
-               delete_tape_list(elist); /* tape failed so delete from list */
+    }
+    /*
+     * Authenticate the streams
+     */
+    for (i = 0; i < NSTREAMS; i++) {
+       if (amidxtaped_streams[i].fd == NULL)
+           continue;
+       if (security_stream_auth(amidxtaped_streams[i].fd) < 0) {
+           errstr = newvstrallocf(errstr,
+               _("[could not authenticate %s stream: %s]"),
+               amidxtaped_streams[i].name,
+               security_stream_geterror(amidxtaped_streams[i].fd));
+           goto connect_error;
+       }
+    }
+
+    /*
+     * The CTLFD and DATAFD streams are mandatory.  If we didn't get
+     * them, complain.
+     */
+    if (amidxtaped_streams[CTLFD].fd == NULL) {
+        errstr = newvstrallocf(errstr, _("[couldn't open CTL streams]"));
+        goto connect_error;
+    }
+    if (amidxtaped_streams[DATAFD].fd == NULL) {
+        errstr = newvstrallocf(errstr, _("[couldn't open DATA streams]"));
+        goto connect_error;
+    }
+
+    /* everything worked */
+    *response_error = 0;
+    return;
+
+parse_error:
+    if (extra) {
+       errstr = newvstrallocf(errstr,
+                         _("[parse of reply message failed: %s]"), extra);
+    } else {
+       errstr = newvstrallocf(errstr,
+                         _("[parse of reply message failed: (no additional information)"));
+    }
+    amfree(extra);
+    *response_error = 2;
+    return;
+
+connect_error:
+    stop_amidxtaped();
+    *response_error = 1;
+}
+
+/*
+ * This is called when everything needs to shut down so event_loop()
+ * will exit.
+ */
+static void
+stop_amidxtaped(void)
+{
+    int i;
+
+    for (i = 0; i < NSTREAMS; i++) {
+        if (amidxtaped_streams[i].fd != NULL) {
+            security_stream_close(amidxtaped_streams[i].fd);
+            amidxtaped_streams[i].fd = NULL;
+        }
+    }
+}
+
+static char* ctl_buffer = NULL;
+/* gets a "line" from server and put in server_line */
+/* server_line is terminated with \0, \r\n is striped */
+/* returns -1 if error */
+
+int
+get_amidxtaped_line(void)
+{
+    ssize_t size;
+    char *newbuf, *s;
+    void *buf;
+
+    amfree(amidxtaped_line);
+    if (!ctl_buffer)
+       ctl_buffer = stralloc("");
+
+    while (!strstr(ctl_buffer,"\r\n")) {
+       if (amidxtaped_streams[CTLFD].fd == NULL)
+           return -1;
+
+        size = security_stream_read_sync(amidxtaped_streams[CTLFD].fd, &buf);
+        if(size < 0) {
+            return -1;
+        }
+        else if(size == 0) {
+            return -1;
+        }
+        newbuf = alloc(strlen(ctl_buffer)+size+1);
+        strncpy(newbuf, ctl_buffer, (size_t)(strlen(ctl_buffer) + size + 1));
+        memcpy(newbuf+strlen(ctl_buffer), buf, (size_t)size);
+        newbuf[strlen(ctl_buffer)+size] = '\0';
+        amfree(ctl_buffer);
+        ctl_buffer = newbuf;
+       amfree(buf);
+    }
+
+    s = strstr(ctl_buffer,"\r\n");
+    *s = '\0';
+    newbuf = stralloc(s+2);
+    amidxtaped_line = stralloc(ctl_buffer);
+    amfree(ctl_buffer);
+    ctl_buffer = newbuf;
+    return 0;
+}
+
+
+static void
+read_amidxtaped_data(
+    void *     cookie,
+    void *     buf,
+    ssize_t    size)
+{
+    ctl_data_t *ctl_data = (ctl_data_t *)cookie;
+    assert(cookie != NULL);
+
+    if (size < 0) {
+       errstr = newstralloc2(errstr, _("amidxtaped read: "),
+                security_stream_geterror(amidxtaped_streams[DATAFD].fd));
+       return;
+    }
+
+    /*
+     * EOF.  Stop and return.
+     */
+    if (size == 0) {
+       security_stream_close(amidxtaped_streams[DATAFD].fd);
+       amidxtaped_streams[DATAFD].fd = NULL;
+       /*
+        * If the mesg fd has also shut down, then we're done.
+        */
+       return;
+    }
+
+    assert(buf != NULL);
+
+    if (ctl_data->header_done == 0) {
+       GPtrArray  *errarray;
+       g_option_t  g_options;
+       data_path_t data_path_set = DATA_PATH_AMANDA;
+
+       /* parse the file header */
+       fh_init(&ctl_data->file);
+       parse_file_header(buf, &ctl_data->file, (size_t)size);
+
+       /* call backup_support_option */
+       g_options.config = get_config_name();
+       g_options.hostname = dump_hostname;
+       if (strcmp(ctl_data->file.program, "APPLICATION") == 0) {
+           if (dump_dle) {
+               ctl_data->bsu = backup_support_option(ctl_data->file.application,
+                                                     &g_options,
+                                                     ctl_data->file.disk,
+                                                     dump_dle->device,
+                                                     &errarray);
+           } else {
+               ctl_data->bsu = backup_support_option(ctl_data->file.application,
+                                                     &g_options,
+                                                     ctl_data->file.disk, NULL,
+                                                     &errarray);
            }
-           else { /* RETRY_TAPE */
+           if (!ctl_data->bsu) {
+               guint  i;
+               for (i=0; i < errarray->len; i++) {
+                   char *line;
+                   line = g_ptr_array_index(errarray, i);
+                   g_fprintf(stderr, "%s\n", line);
+               }
+               g_ptr_array_free_full(errarray);
+               exit(1);
            }
+           data_path_set = ctl_data->bsu->data_path_set;
        }
-       else {
-           delete_tape_list(elist);    /* tape done so delete from list */
+       /* handle backup_support_option failure */
+
+       ctl_data->header_done = 1;
+       if (!ask_file_overwrite(ctl_data)) {
+           if (am_has_feature(tapesrv_features, fe_amidxtaped_abort)) {
+               send_to_tape_server(amidxtaped_streams[CTLFD].fd, "ABORT");
+           }
+           stop_amidxtaped();
+           return;
+       }
+
+       if (am_has_feature(tapesrv_features, fe_amidxtaped_datapath)) {
+           char       *msg;
+           /* send DATA-PATH request */
+           msg = stralloc("AVAIL-DATAPATH");
+           if (data_path_set & DATA_PATH_AMANDA)
+               vstrextend(&msg, " AMANDA", NULL);
+           if (data_path_set & DATA_PATH_DIRECTTCP)
+               vstrextend(&msg, " DIRECT-TCP", NULL);
+           send_to_tape_server(amidxtaped_streams[CTLFD].fd, msg);
+           amfree(msg);
+       } else {
+           start_processing_data(ctl_data);
+       }
+    } else {
+       ctl_data->bytes_read += size;
+       /* Only the data is sent to the child */
+       /*
+        * We ignore errors while writing to the index file.
+        */
+       (void)full_write(ctl_data->child_pipe[1], buf, (size_t)size);
+        security_stream_read(amidxtaped_streams[DATAFD].fd,
+                            read_amidxtaped_data, cookie);
+    }
+}
+
+static gboolean
+ask_file_overwrite(
+    ctl_data_t *ctl_data)
+{
+    char *restore_dir = NULL;
+
+    if (ctl_data->file.dumplevel == 0) {
+       property_t *property = g_hash_table_lookup(proplist, "directory");
+       if (property && property->values && property->values->data) {
+           /* take first property value */
+           restore_dir = strdup(property->values->data);
+       }
+       if (samba_extract_method == SAMBA_SMBCLIENT ||
+           (ctl_data->bsu &&
+            ctl_data->bsu->recover_path == RECOVER_PATH_REMOTE)) {
+           if (!restore_dir) {
+               restore_dir = g_strdup(ctl_data->file.disk);
+           }
+           g_printf(_("Restoring files into target host %s\n"), restore_dir);
+       } else {
+           if (!restore_dir) {
+               restore_dir = g_get_current_dir();
+           }
+           g_printf(_("Restoring files into directory %s\n"), restore_dir);
+       }
+
+       /* Collect files to delete befause of a bug in gnutar */
+       if (strcmp(ctl_data->file.program, "GNUTAR") == 0 ||
+           (strcmp(ctl_data->file.program, "APPLICATION") == 0 &&
+            strcmp(ctl_data->file.application, "amgtar") == 0)) {
+           check_file_overwrite(restore_dir);
+       } else {
+           g_printf(_("All existing files in %s can be deleted\n"),
+                    restore_dir);
+       }
+
+       if (!okay_to_continue(0,0,0)) {
+           free_unlink_list();
+           amfree(restore_dir);
+           return FALSE;
+       }
+       g_printf("\n");
+
+       /* delete the files for gnutar */
+       if (unlink_list) {
+           if (!do_unlink_list()) {
+               g_fprintf(stderr, _("Can't recover because I can't cleanup the restore directory (%s)\n"),
+                         restore_dir);
+               free_unlink_list();
+               amfree(restore_dir);
+               return FALSE;
+           }
+           free_unlink_list();
        }
+       amfree(restore_dir);
+    }
+    return TRUE;
+}
+
+static void
+start_processing_data(
+    ctl_data_t *ctl_data)
+{
+    if (pipe(ctl_data->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 ((ctl_data->pid = fork()) == 0) {
+       /* this is the child process */
+       /* never gets out of this clause */
+       aclose(ctl_data->child_pipe[1]);
+       extract_files_child(ctl_data);
+       /*NOTREACHED*/
+    }
+       
+    if (ctl_data->pid == -1) {
+       errstr = newstralloc(errstr, _("writer_intermediary - error forking child"));
+       g_printf(_("writer_intermediary - error forking child"));
+       return;
+    }
+    aclose(ctl_data->child_pipe[0]);
+    security_stream_read(amidxtaped_streams[DATAFD].fd, read_amidxtaped_data,
+                        ctl_data);
+    if (am_has_feature(tapesrv_features, fe_amidxtaped_datapath)) {
+       send_to_tape_server(amidxtaped_streams[CTLFD].fd, "DATAPATH-OK");
     }
 }