/*
 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
 * Copyright (c) 1991-1998, 2000 University of Maryland at College Park
 * Copyright (c) 2007-2012 Zmanda, Inc.  All Rights Reserved.
 * Copyright (c) 2013-2016 Carbonite, Inc.  All Rights Reserved.
 * All Rights Reserved.
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of U.M. not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  U.M. makes no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Authors: the Amanda Development Team.  Its members are listed in a
 * file named AUTHORS, in the root directory of this distribution.
 */
/*
 * $Id$
 *
 * implements the "extract" command in amrecover
 */

#include "amanda.h"
#include "match.h"
#include "amrecover.h"
#include "fileheader.h"
#include "dgram.h"
#include "stream.h"
#include "tapelist.h"
#ifdef SAMBA_CLIENT
#include "findpass.h"
#endif
#include "amutil.h"
#include "conffile.h"
#include "protocol.h"
#include "event.h"
#include "client_util.h"
#include "security.h"
#include "pipespawn.h"

typedef struct EXTRACT_LIST_ITEM {
    char *path;
    char *tpath;
    struct EXTRACT_LIST_ITEM *next;
}
EXTRACT_LIST_ITEM;

typedef struct EXTRACT_LIST {
    char *date;			/* date tape created */
    int  level;			/* level of dump */
    char *tape;			/* tape label */
    off_t fileno;		/* fileno on tape */
    EXTRACT_LIST_ITEM *files;	/* files to get off tape */

    struct EXTRACT_LIST *next;
}
EXTRACT_LIST;

typedef struct cdata_s {
    int             fd;
    FILE           *output;
    char           *name;
    char           *buffer;
    gint64          first;           /* first byte used */
    gint64          size;            /* number of byte use in the buffer */
    gint64          allocated_size ; /* allocated size of the buffer     */
    event_handle_t *event;
} cdata_t;

typedef struct ctl_data_s {
  gboolean                 header_done;
  int                      child_in[2];
  int                      child_out[2];
  int                      child_err[2];
  int                      crc_pipe[2];
  int                      dar_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;
  cdata_t		   decrypt_cdata;
  cdata_t		   decompress_cdata;
  cdata_t		   child_out_cdata;
  cdata_t		   child_err_cdata;
  cdata_t		   dar_cdata;
} ctl_data_t;

typedef struct ctl_state_s {
  int		fd;
  gboolean      state_done;
  gint64        bytes_read;
} ctl_state_t;

#define SKIP_TAPE 2
#define RETRY_TAPE 3

static struct {
    const char *name;
    security_stream_t *fd;
} amidxtaped_streams[] = {
#define CTLFD  0
    { "CTL", NULL },
#define DATAFD  1
    { "DATA", NULL },
#define STATEFD  2
    { "STATE", NULL },
};

typedef struct send_crc_s {
    int      in;
    int      out;
    crc_t    crc;
    GThread *thread;
} send_crc_t;

#define NSTREAMS G_N_ELEMENTS(amidxtaped_streams)

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;
static char header_buf[32768];
static int  header_size = 0;
static int  header_send_size = 0;
static int  stderr_isatty;
static time_t last_time = 0;
static gboolean  last_is_size;
static crc_t crc_in;
static send_crc_t native_crc;
static crc_t network_crc;
static char *state_filename = NULL;
static int use_dar = 0;
static int got_use_dar = 0;
static int send_use_dar = 0;
static ctl_data_t  ctl_data;
static ctl_state_t ctl_state;
static data_path_t data_path_set;


/* global pid storage for interrupt handler */
pid_t extract_restore_child_pid = -1;

static EXTRACT_LIST *extract_list = NULL;
static const security_driver_t *amidxtaped_secdrv;

unsigned short samba_extract_method = SAMBA_TAR;

#define READ_TIMEOUT	240*60

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 handle_child_out(void *);
static void handle_dar_command(void *);
static void read_amidxtaped_state(void *, void *, ssize_t);
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);
static gpointer handle_crc_thread(gpointer data);
void try_send_use_dar(void);

/*
 * Function:  ssize_t read_buffer(datafd, buffer, buflen, timeout_s)
 *
 * Description:
 *	read data from input file desciptor waiting up to timeout_s
 *	seconds before returning data.
 *
 * Inputs:
 *	datafd    - File descriptor to read from.
 *	buffer    - Buffer to read into.
 *	buflen    - Maximum number of bytes to read into buffer.
 *	timeout_s - Seconds to wait before returning what was already read.
 *
 * Returns:
 *      >0	  - Number of data bytes in buffer.
 *       0	  - EOF
 *      -1        - errno == ETIMEDOUT if no data available in specified time.
 *                  errno == ENFILE if datafd is invalid.
 *                  otherwise errno is set by select or read..
 */

static ssize_t
read_buffer(
    int		datafd,
    char *	buffer,
    size_t	buflen,
    long	timeout_s)
{
    ssize_t size = 0;
    SELECT_ARG_TYPE readset;
    struct timeval timeout;
    char *dataptr;
    ssize_t spaceleft;
    int nfound;

    if(datafd < 0 || datafd >= (int)FD_SETSIZE) {
	errno = EMFILE;					/* out of range */
	return -1;
    }

    dataptr = buffer;
    spaceleft = (ssize_t)buflen;

    do {
        FD_ZERO(&readset);
        FD_SET(datafd, &readset);
        timeout.tv_sec = timeout_s;
        timeout.tv_usec = 0;
        nfound = select(datafd+1, &readset, NULL, NULL, &timeout);
        if(nfound < 0 ) {
            /* Select returned an error. */
	    g_fprintf(stderr,_("select error: %s\n"), strerror(errno));
            size = -1;
	    break;
        }

	if (nfound == 0) {
            /* Select timed out. */
            if (timeout_s != 0)  {
                /* Not polling: a real read timeout */
                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;
	    break;
        }

	if(!FD_ISSET(datafd, &readset))
	    continue;

        /* Select says data is available, so read it.  */
        size = read(datafd, dataptr, (size_t)spaceleft);
        if (size < 0) {
	    if ((errno == EINTR) || (errno == EAGAIN)) {
		continue;
	    }
	    if (errno != EPIPE) {
	        g_fprintf(stderr, _("read_buffer: read error - %s"),
		    strerror(errno));
	        break;
	    }
	    size = 0;
	}
        spaceleft -= size;
        dataptr += size;
    } while ((size > 0) && (spaceleft > 0));

    return ((((ssize_t)buflen-spaceleft) > 0) ? ((ssize_t)buflen-spaceleft) : size);
}


EXTRACT_LIST *
first_tape_list(void)
{
    return extract_list;
}

EXTRACT_LIST *
next_tape_list(
    /*@keep@*/EXTRACT_LIST *list)
{
    if (list == NULL)
	return NULL;
    return list->next;
}

static void
clear_tape_list(
    EXTRACT_LIST *	tape_list)
{
    EXTRACT_LIST_ITEM *this, *next;


    this = tape_list->files;
    while (this != NULL)
    {
	next = this->next;
        amfree(this->path);
        amfree(this->tpath);
        amfree(this);
	this = next;
    }
    tape_list->files = NULL;
}


/* remove a tape list from the extract list, clearing the tape list
   beforehand if necessary */
void
delete_tape_list(
    EXTRACT_LIST *tape_list)
{
    EXTRACT_LIST *this, *prev;

    if (tape_list == NULL)
        return;

    /* is it first on the list? */
    if (tape_list == extract_list)
    {
	extract_list = tape_list->next;
	clear_tape_list(tape_list);
        amfree(tape_list->date);
        amfree(tape_list->tape);
	amfree(tape_list);
	return;
    }

    /* so not first on list - find it and delete */
    prev = extract_list;
    this = extract_list->next;
    while (this != NULL)
    {
	if (this == tape_list)
	{
	    prev->next = tape_list->next;
	    clear_tape_list(tape_list);
            amfree(tape_list->date);
            amfree(tape_list->tape);
	    amfree(tape_list);
	    return;
	}
	prev = this;
	this = this->next;
    }
    /*NOTREACHED*/
}


/* return the number of files on a tape's list */
int
length_of_tape_list(
    EXTRACT_LIST *tape_list)
{
    EXTRACT_LIST_ITEM *fn;
    int n;

    n = 0;
    for (fn = tape_list->files; fn != NULL; fn = fn->next)
	n++;

    return n;
}


void
clear_extract_list(void)
{
    while (extract_list != NULL)
	delete_tape_list(extract_list);
}


void
clean_tape_list(
    EXTRACT_LIST *tape_list)
{
    EXTRACT_LIST_ITEM *fn1, *pfn1, *ofn1;
    EXTRACT_LIST_ITEM *fn2, *pfn2, *ofn2;
    int remove_fn1;
    int remove_fn2;

    pfn1 = NULL;
    fn1 = tape_list->files;
    while (fn1 != NULL) {
	remove_fn1 = 0;

	pfn2 = fn1;
	fn2 = fn1->next;
	while (fn2 != NULL && remove_fn1 == 0) {
	    remove_fn2 = 0;
	    if(g_str_equal(fn1->path, fn2->path)) {
		remove_fn2 = 1;
	    } else if (g_str_has_prefix(fn2->path, fn1->path) &&
		       ((strlen(fn2->path) > strlen(fn1->path) &&
			 fn2->path[strlen(fn1->path)] == '/') ||
		       (fn1->path[strlen(fn1->path)-1] == '/'))) {
		remove_fn2 = 1;
	    } else if (g_str_has_prefix(fn1->path, fn2->path) &&
		       ((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->tpath);
		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->tpath, fn2->tpath);
	    ofn1 = fn1;
	    fn1 = fn1->next;
	    amfree(ofn1->path);
	    amfree(ofn1->tpath);
	    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 (g_str_equal(*dir, ".")) {
	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 = g_malloc(sizeof(*unlink_list));
	unlink_list->path = g_strdup(path);
	unlink_list->next = NULL;
    } else {
	for (ul = unlink_list; ul != NULL; ul = ul->next) {
	    if (g_str_equal(ul->path, path))
		return 0;
	}
	ul = g_malloc(sizeof(*ul));
	ul->path = g_strdup(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 = g_strconcat(dir, fn->path, NULL);
	    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 = g_strconcat(dir, fn->path, NULL);
	    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(
    DIR_ITEM *ditem)
{
    EXTRACT_LIST *this, *this1;
    EXTRACT_LIST_ITEM *that, *curr;
    char *ditem_path;

    ditem_path = g_strdup(ditem->path);
    clean_pathname(ditem_path);

    for (this = extract_list; this != NULL; this = this->next)
    {
	/* see if this is the list for the tape */	
	if (this->level == ditem->level && g_str_equal(this->tape,
                                                       ditem->tape))
	{
	    /* yes, so add to list */
	    curr=this->files;
	    while(curr!=NULL)
	    {
		if (g_str_equal(curr->path, ditem_path)) {
		    g_free(ditem_path);
		    return 1;
		}
		curr=curr->next;
	    }
	    that = (EXTRACT_LIST_ITEM *)g_malloc(sizeof(EXTRACT_LIST_ITEM));
            that->path = ditem_path;
            that->tpath = clean_pathname(g_strdup(ditem->tpath));
	    that->next = this->files;
	    this->files = that;		/* add at front since easiest */
	    return 0;
	}
    }

    /* so this is the first time we have seen this tape */
    this = (EXTRACT_LIST *)g_malloc(sizeof(EXTRACT_LIST));
    this->tape = g_strdup(ditem->tape);
    this->level = ditem->level;
    this->fileno = ditem->fileno;
    this->date = g_strdup(ditem->date);
    that = (EXTRACT_LIST_ITEM *)g_malloc(sizeof(EXTRACT_LIST_ITEM));
    that->path = ditem_path;
    that->tpath = clean_pathname(g_strdup(ditem->tpath));
    that->next = NULL;
    this->files = that;

    /* add this in date increasing order          */
    /* because restore must be done in this order */
    /* add at begining */
    if(extract_list==NULL || strcmp(this->date,extract_list->date) < 0)
    {
	this->next = extract_list;
	extract_list = this;
	return 0;
    }
    for (this1 = extract_list; this1->next != NULL; this1 = this1->next)
    {
	/* add in the middle */
	if(strcmp(this->date,this1->next->date) < 0)
	{
	    this->next = this1->next;
	    this1->next = this;
	    return 0;
	}
    }
    /* add at end */
    this->next = NULL;
    this1->next = this;
    return 0;
}


/* returns -1 if error */
/* returns  0 on deletion */
/* returns  1 if not there */
static int
delete_extract_item(
    DIR_ITEM *ditem)
{
    EXTRACT_LIST *this;
    EXTRACT_LIST_ITEM *that, *prev;
    char *ditem_path = NULL;

    ditem_path = g_strdup(ditem->path);
    clean_pathname(ditem_path);

    for (this = extract_list; this != NULL; this = this->next)
    {
	/* see if this is the list for the tape */	
	if (this->level == ditem->level && g_str_equal(this->tape,
                                                       ditem->tape))
	{
	    /* yes, so find file on list */
	    that = this->files;
	    if (g_str_equal(that->path, ditem_path))
	    {
		/* first on list */
		this->files = that->next;
                amfree(that->path);
                amfree(that->tpath);
		amfree(that);
		/* if list empty delete it */
		if (this->files == NULL)
		    delete_tape_list(this);
		amfree(ditem_path);
		return 0;
	    }
	    prev = that;
	    that = that->next;
	    while (that != NULL)
	    {
		if (g_str_equal(that->path, ditem_path))
		{
		    prev->next = that->next;
                    amfree(that->path);
                    amfree(that->tpath);
		    amfree(that);
		    amfree(ditem_path);
		    return 0;
		}
		prev = that;
		that = that->next;
	    }
	    amfree(ditem_path);
	    return 1;
	}
    }

    amfree(ditem_path);
    return 1;
}

static char *
merge_path(
    char *path1,
    char *path2)
{
    char *result;
    int len = strlen(path1);
    if (path1[len-1] == '/' && path2[0] == '/') {
	result = g_strconcat(path1, path2 + 1, NULL);
    } else if (path1[len-1] != '/' && path2[0] != '/') {
	result = g_strjoin(NULL, path1, "/", path2, NULL);
    } else {
	result = g_strconcat(path1, path2, NULL);
    }
    return result;
}

void
add_glob(
    char *	glob)
{
    char *regex;
    char *regex_path;
    char *s;
    char *uqglob;
    char *dir;
    char *sdir = NULL;
    int   result = 1;

    if (disk_path == NULL) {
	g_printf(_("Must select directory before adding files\n"));
	return;
    }

    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 = g_strdup(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(
    char *	regex)
{
    char *s;
    char *dir;
    char *sdir = NULL;
    char *uqregex;
    char *newregex;
    int   result = 1;

    if (disk_path == NULL) {
	g_printf(_("Must select directory before adding files\n"));
	return;
    }

    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(
    char *	path,
    char *	regex)
{
    DIR_ITEM *ditem, lditem;
    char *tpath_on_disk = NULL;
    char *cmd = NULL;
    char *err = NULL;
    int i;
    ssize_t j;
    char *dir_undo, dir_undo_ch = '\0';
    char *ditem_path = NULL;
    char *qditem_path = NULL;
    char *l = NULL;
    int  added;
    char *s, *fp, *quoted;
    int ch;
    int found_one;
    int dir_entries;

    if (disk_path == NULL) {
	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);

    if(g_str_equal(regex, "/[/]*$")) {	/* "/" behave like "." */
	regex = "\\.[/]*$";
    }
    else if(g_str_equal(regex, "[^/]*[/]*$")) {		/* "*" */
	regex = "([^/.]|\\.[^/]+|[^/.][^/]*)[/]*$";
    } else {
	/* remove "/" at end of path */
	j = (ssize_t)(strlen(regex) - 1);
	while(j >= 0 && regex[j] == '/')
	    regex[j--] = '\0';
    }

    /* convert path (assumed in cwd) to one on disk */
    if (g_str_equal(disk_path, "/")) {
        if (*regex == '/') {
	    /* No mods needed if already starts with '/' */
	    tpath_on_disk = g_strdup(regex);
	} else {
	    /* Prepend '/' */
	    tpath_on_disk = g_strconcat("/", regex, NULL);
	}
    } else {
	char *clean_disk_tpath = clean_regex(disk_tpath, 0);
	tpath_on_disk = g_strjoin(NULL, clean_disk_tpath, "/", regex, NULL);
	amfree(clean_disk_tpath);
    }

    dbprintf(_("add_file: Converted path=\"%s\" to tpath_on_disk=\"%s\"\n"),
	      regex, tpath_on_disk);

    found_one = 0;
    dir_entries = 0;
    for (ditem=get_dir_list(); ditem!=NULL; ditem=get_next_dir_item(ditem))
    {
	dir_entries++;
	quoted = quote_string(ditem->tpath);
	dbprintf(_("add_file: Pondering ditem->path=%s\n"), quoted);
	amfree(quoted);
	if (match(tpath_on_disk, ditem->tpath))
	{
	    found_one = 1;
	    j = (ssize_t)strlen(ditem->tpath);
	    if((j > 0 && ditem->tpath[j-1] == '/')
	       || (j > 1 && ditem->tpath[j-2] == '/' && ditem->tpath[j-1] == '.'))
	    {	/* It is a directory */
		g_free(ditem_path);
		ditem_path = g_strdup(ditem->path);
		clean_pathname(ditem_path);

		qditem_path = quote_string(ditem_path);
		g_free(cmd);
		cmd = g_strconcat("ORLD ", qditem_path, NULL);
		amfree(qditem_path);
		if(send_command(cmd) == -1) {
		    amfree(cmd);
		    amfree(ditem_path);
		    amfree(tpath_on_disk);
		    exit(1);
		}
		amfree(cmd);
		cmd = NULL;
		/* skip preamble */
		if ((i = get_reply_line()) == -1) {
		    amfree(ditem_path);
		    amfree(tpath_on_disk);
		    exit(1);
		}
		if(i==0) {		/* assume something wrong */
		    amfree(ditem_path);
		    amfree(tpath_on_disk);
		    amfree(lditem.path);
		    amfree(lditem.date);
		    amfree(lditem.tape);
		    l = reply_line();
		    g_printf("%s\n", l);
		    return;
		}
		dir_undo = NULL;
		added=0;
                g_free(lditem.path);
                g_free(lditem.tpath);
                lditem.path = g_strdup(ditem->path);
                lditem.tpath = g_strdup(ditem->tpath);
		/* skip the last line -- duplicate of the preamble */

		while ((i = get_reply_line()) != 0) {
		    if (i == -1) {
			amfree(ditem_path);
		        amfree(tpath_on_disk);
			exit(1);
		    }
		    if(err) {
			if(cmd == NULL) {
			    if(dir_undo) *dir_undo = dir_undo_ch;
			    dir_undo = NULL;
			    cmd = g_strdup(l);	/* save for error report */
			}
			continue;	/* throw the rest of the lines away */
		    }
		    l=reply_line();
		    if (!server_happy()) {
			puts(l);
			continue;
		    }

		    s = l;
		    if(strncmp_const_skip(l, "201-", s, ch) != 0) {
			err = _("bad reply: not 201-");
			continue;
		    }
		    ch = *s++;

		    skip_whitespace(s, ch);
		    if(ch == '\0') {
			err = _("bad reply: missing date field");
			continue;
		    }
                    fp = s-1;
                    skip_non_whitespace(s, ch);
                    s[-1] = '\0';
                    g_free(lditem.date);
                    lditem.date = g_strdup(fp);
                    s[-1] = (char)ch;

		    skip_whitespace(s, ch);
		    if(ch == '\0' || sscanf(s - 1, "%d", &lditem.level) != 1) {
			err = _("bad reply: cannot parse level field");
			continue;
		    }
		    skip_integer(s, ch);

		    skip_whitespace(s, ch);
		    if(ch == '\0') {
			err = _("bad reply: missing tape field");
			continue;
		    }
                    fp = s-1;
                    skip_quoted_string(s, ch);
                    s[-1] = '\0';
		    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, "%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");
			continue;
		    }
		    skip_quoted_string(s, ch);
		    dir_undo = s - 1;
		    dir_undo_ch = *dir_undo;
		    *dir_undo = '\0';

		    switch(add_extract_item(&lditem)) {
		    case -1:
			g_printf(_("System error\n"));
			dbprintf(_("add_file: (Failed) System error\n"));
			break;

		    case  0:
			quoted = quote_string(lditem.tpath);
			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;
		    }
		}
		if(!server_happy()) {
		    puts(reply_line());
		} else if(err) {
		    if (*err)
			puts(err);
		    if (cmd)
			puts(cmd);
		} else if(added == 0) {
		    quoted = quote_string(ditem_path);
		    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:
		    g_printf(_("System error\n"));
		    dbprintf(_("add_file: (Failed) System error\n"));
		    break;

		case  0:
		    quoted = quote_string(ditem->tpath);
		    g_printf(_("Added file %s\n"), quoted);
		    dbprintf(_("add_file: (Successful) Added %s\n"), quoted);
		    amfree(quoted);
		    break;

		case  1:
		    quoted = quote_string(ditem->tpath);
		    g_printf(_("File %s already added\n"), quoted);
		    dbprintf(_("add_file: file %s already added\n"), quoted);
		    amfree(quoted);
		}
	    }
	}
    }

    amfree(cmd);
    amfree(ditem_path);
    amfree(tpath_on_disk);

    amfree(lditem.path);
    amfree(lditem.tpath);
    amfree(lditem.date);
    amfree(lditem.tape);

    if(! found_one) {
	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(
    char *	glob)
{
    char *regex;
    char *regex_path;
    char *s;
    char *uqglob;
    char *newglob;
    char *dir;
    char *sdir = NULL;
    int   result = 1;

    if (disk_path == NULL) {
	g_printf(_("Must select directory before adding files\n"));
	return;
    }

    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 = g_strdup(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(
    char *	regex)
{
    char *s;
    char *dir;
    char *sdir = NULL;
    char *uqregex;
    char *newregex;
    int   result = 1;

    if (disk_path == NULL) {
	g_printf(_("Must select directory before adding files\n"));
	return;
    }

    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(
    char *	tpath,
    char *	regex)
{
    DIR_ITEM *ditem, lditem;
    char *tpath_on_disk = NULL;
    char *cmd = NULL;
    char *err = NULL;
    int i;
    ssize_t j;
    char *date;
    char *tape, *tape_undo, tape_undo_ch = '\0';
    char *dir_undo, dir_undo_ch = '\0';
    int  level = 0;
    char *ditem_path = NULL;
    char *ditem_tpath = NULL;
    char *qditem_path;
    char *l = NULL;
    int  deleted;
    char *s;
    int ch;
    int found_one;
    char *quoted;

    if (disk_path == NULL) {
	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"), tpath);

    if (g_str_equal(regex, "[^/]*[/]*$")) {
	/* 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 (g_str_equal(disk_path, "/")) {
        if (*regex == '/') {
	    if (g_str_equal(regex, "/[/]*$")) {
		/* We want "/" to match the directory itself: "/." */
		tpath_on_disk = g_strdup("/\\.[/]*$");
	    } else {
		/* No mods needed if already starts with '/' */
		tpath_on_disk = g_strdup(regex);
	    }
	} else {
	    /* Prepend '/' */
	    tpath_on_disk = g_strconcat("/", regex, NULL);
	}
    } else {
	char *clean_disk_tpath = clean_regex(disk_tpath, 0);
	tpath_on_disk = g_strjoin(NULL, clean_disk_tpath, "/", regex, NULL);
	amfree(clean_disk_tpath);
    }

    dbprintf(_("delete_file: Converted path=\"%s\" to tpath_on_disk=\"%s\"\n"),
	      regex, tpath_on_disk);
    found_one = 0;
    for (ditem=get_dir_list(); ditem!=NULL; ditem=get_next_dir_item(ditem))
    {
	quoted = quote_string(ditem->tpath);
	dbprintf(_("delete_file: Pondering ditem->path=%s\n"), quoted);
	amfree(quoted);
	if (match(tpath_on_disk, ditem->tpath))
	{
	    found_one = 1;
	    j = (ssize_t)strlen(ditem->tpath);
	    if((j > 0 && ditem->tpath[j-1] == '/')
	       || (j > 1 && ditem->tpath[j-2] == '/' && ditem->tpath[j-1] == '.'))
	    {	/* It is a directory */
		g_free(ditem_path);
		g_free(ditem_tpath);
		ditem_path = g_strdup(ditem->path);
		ditem_tpath = g_strdup(ditem->tpath);
		clean_pathname(ditem_path);
		clean_pathname(ditem_tpath);

		qditem_path = quote_string(ditem_path);
		g_free(cmd);
		cmd = g_strconcat("ORLD ", qditem_path, NULL);
		amfree(qditem_path);
		if(send_command(cmd) == -1) {
		    amfree(cmd);
		    amfree(ditem_path);
		    amfree(tpath_on_disk);
		    exit(1);
		}
		amfree(cmd);
		/* skip preamble */
		if ((i = get_reply_line()) == -1) {
		    amfree(ditem_path);
		    amfree(ditem_tpath);
		    amfree(tpath_on_disk);
		    exit(1);
		}
		if(i==0)		/* assume something wrong */
		{
		    amfree(ditem_path);
		    amfree(ditem_tpath);
		    amfree(tpath_on_disk);
		    amfree(lditem.path);
		    l = reply_line();
		    g_printf("%s\n", l);
		    return;
		}
		deleted=0;
                g_free(lditem.path);
                g_free(lditem.tpath);
                lditem.path = g_strdup(ditem->path);
                lditem.tpath = g_strdup(ditem->tpath);
		amfree(cmd);
		tape_undo = dir_undo = NULL;
		/* skip the last line -- duplicate of the preamble */
		while ((i = get_reply_line()) != 0)
		{
		    if (i == -1) {
			amfree(ditem_path);
			amfree(tpath_on_disk);
			exit(1);
		    }
		    if(err) {
			if(cmd == NULL) {
			    if(tape_undo) *tape_undo = tape_undo_ch;
			    if(dir_undo) *dir_undo = dir_undo_ch;
			    tape_undo = dir_undo = NULL;
			    cmd = g_strdup(l);	/* save for the error report */
			}
			continue;	/* throw the rest of the lines away */
		    }
		    l=reply_line();
		    if (!server_happy()) {
			puts(l);
			continue;
		    }

		    s = l;
		    if(strncmp_const_skip(l, "201-", s, ch) != 0) {
			err = _("bad reply: not 201-");
			continue;
		    }
		    ch = *s++;

		    skip_whitespace(s, ch);
		    if(ch == '\0') {
			err = _("bad reply: missing date field");
			continue;
		    }
		    date = s - 1;
		    skip_non_whitespace(s, ch);
		    *(s - 1) = '\0';

		    skip_whitespace(s, ch);
		    if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
			err = _("bad reply: cannot parse level field");
			continue;
		    }
		    skip_integer(s, ch);

		    skip_whitespace(s, ch);
		    if(ch == '\0') {
			err = _("bad reply: missing tape field");
			continue;
		    }
		    tape = s - 1;
		    skip_non_whitespace(s, ch);
		    tape_undo = s - 1;
		    tape_undo_ch = *tape_undo;
		    *tape_undo = '\0';

		    if(am_has_feature(indexsrv_features, fe_amindexd_fileno_in_ORLD)) {
			long long fileno_ = (long long)0;
			skip_whitespace(s, ch);
			if(ch == '\0' ||
			   sscanf(s - 1, "%lld", &fileno_) != 1) {
			    err = _("bad reply: cannot parse fileno field");
			    continue;
			}
			skip_integer(s, ch);
		    }

		    skip_whitespace(s, ch);
		    if(ch == '\0') {
			err = _("bad reply: missing directory field");
			continue;
		    }
		    skip_non_whitespace(s, ch);
		    dir_undo = s - 1;
		    dir_undo_ch = *dir_undo;
		    *dir_undo = '\0';

                    g_free(lditem.date);
                    lditem.date = g_strdup(date);
		    lditem.level=level;
                    g_free(lditem.tape);
                    lditem.tape = unquote_string(tape);
		    switch(delete_extract_item(&lditem)) {
		    case -1:
			g_printf(_("System error\n"));
			dbprintf(_("delete_file: (Failed) System error\n"));
			break;
		    case  0:
			g_printf(_("Deleted dir %s at date %s\n"), ditem_tpath, date);
			dbprintf(_("delete_file: (Successful) Deleted dir %s at date %s\n"),
				  ditem_tpath, date);
			deleted=1;
			break;
		    case  1:
			break;
		    }
		}
		if(!server_happy()) {
		    puts(reply_line());
		} else if(err) {
		    if (*err)
			puts(err);
		    if (cmd)
			puts(cmd);
		} else if(deleted == 0) {
		    g_printf(_("Warning - dir '%s' not on tape list\n"),
			   ditem_tpath);
		    dbprintf(_("delete_file: dir '%s' not on tape list\n"),
			      ditem_tpath);
		}
	    }
	    else
	    {
		switch(delete_extract_item(ditem)) {
		case -1:
		    g_printf(_("System error\n"));
		    dbprintf(_("delete_file: (Failed) System error\n"));
		    break;
		case  0:
		    g_printf(_("Deleted %s\n"), ditem->tpath);
		    dbprintf(_("delete_file: (Successful) Deleted %s\n"),
			      ditem->tpath);
		    break;
		case  1:
		    g_printf(_("Warning - file '%s' not on tape list\n"),
			   ditem->tpath);
		    dbprintf(_("delete_file: file '%s' not on tape list\n"),
			      ditem->tpath);
		    break;
		}
	    }
	}
    }
    amfree(cmd);
    amfree(ditem_path);
    amfree(ditem_tpath);
    amfree(tpath_on_disk);
    amfree(lditem.path);

    if(! found_one) {
	g_printf(_("File %s doesn't exist in directory\n"), tpath);
	dbprintf(_("delete_file: (Failed) File %s doesn't exist in directory\n"),
	          tpath);
    }
}


/* print extract list into file. If NULL ptr passed print to screen */
void
display_extract_list(
    char *	file)
{
    EXTRACT_LIST *this;
    EXTRACT_LIST_ITEM *that;
    FILE *fp;
    char *pager;
    char *pager_command;
    char *uqfile;

    if (file == NULL)
    {
	if ((pager = getenv("PAGER")) == NULL)
	{
	    pager = "more";
	}
	/*
	 * Set up the pager command so if the pager is terminated, we do
	 * not get a SIGPIPE back.
	 */
	pager_command = g_strconcat(pager, " ; /bin/cat > /dev/null", NULL);
	if ((fp = popen(pager_command, "w")) == NULL)
	{
	    g_printf(_("Warning - can't pipe through %s\n"), pager);
	    fp = stdout;
	}
	amfree(pager_command);
    }
    else
    {
	uqfile = unquote_string(file);
	if ((fp = fopen(uqfile, "w")) == NULL)
	{
	    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)
    {
	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)
	    g_fprintf(fp, "\t%s\n", that->tpath);
    }

    if (file == NULL) {
	apclose(fp);
    } else {
	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(void)
{
    return (extract_list != NULL);
}


/* prints continue prompt and waits for response,
   returns 0 if don't, non-0 if do */
static int
okay_to_continue(
    int	allow_tape,
    int	allow_skip,
    int	allow_retry)
{
    int ch;
    int ret = -1;
    char *line = NULL;
    char *s;
    char *prompt;
    int get_device;

    get_device = 0;
    while (ret < 0) {
	if (get_device) {
	    prompt = _("New device name [?]: ");
	} else if (allow_tape && allow_skip) {
	    prompt = _("Continue [?/Y/n/s/d]? ");
	} else if (allow_tape && !allow_skip) {
	    prompt = _("Continue [?/Y/n/d]? ");
	} else if (allow_retry) {
	    prompt = _("Continue [?/Y/n/r]? ");
	} else {
	    prompt = _("Continue [?/Y/n]? ");
	}
	fputs(prompt, stdout);
	fflush(stdout); fflush(stderr);
	amfree(line);
	if ((line = pgets(stdin)) == NULL) {
	    putchar('\n');
	    clearerr(stdin);
	    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' && g_ascii_isspace(ch)) {
	    (void)ch;	/* Quiet empty loop compiler warning */
	}
	if (ch == '?') {
	    if (get_device) {
		g_printf(_("Enter a new device name or \"default\"\n"));
	    } else {
		g_printf(_("Enter \"y\"es to continue, \"n\"o to stop"));
		if(allow_skip) {
		    g_printf(_(", \"s\"kip this tape"));
		}
		if(allow_retry) {
		    g_printf(_(" or \"r\"etry this tape"));
		}
		if (allow_tape) {
		    g_printf(_(" or \"d\" to change to a new device"));
		}
		putchar('\n');
	    }
	} else if (get_device) {
	    char *tmp = g_strdup(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 == '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')) {
	    ret = RETRY_TAPE;
	} else if (allow_skip && (ch == 'S' || ch == 's')) {
	    ret = SKIP_TAPE;
	}
    }
    /*@ignore@*/
    amfree(line);
    /*@end@*/
    return ret;
}

static void
send_to_tape_server(
    security_stream_t *	stream,
    char *		cmd)
{
    char *msg = g_strconcat(cmd, "\r\n", NULL);

    g_debug("send_to_tape_server: %s\n", cmd);
    if (security_stream_write(stream, msg, strlen(msg)) < 0)
    {
	error(_("Error writing to tape server"));
	exit(101);
	/*NOTREACHED*/
    }
    amfree(msg);
}


/* start up connection to tape server and set commands to initiate
   transfer of dump image.
   Return tape server socket on success, -1 on error. */
static int
extract_files_setup(
    char *	label,
    off_t	fsf)
{
    char *disk_regex = NULL;
    char *host_regex = NULL;
    char *clean_datestamp, *ch, *ch1;
    char *tt = NULL;
    char *req;
    int response_error;

    amidxtaped_secdrv = security_getdriver(authopt);
    if (amidxtaped_secdrv == NULL) {
	error(_("no '%s' security driver available for host '%s'"),
	      authopt, tape_server_name);
    }

    /* We assume that amidxtaped support fe_amidxtaped_options_features */
    /*                               and fe_amidxtaped_options_auth     */
    /* We should send a noop to really know                             */
    req = g_strjoin(NULL, "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;
    }

    disk_regex = make_exact_disk_expression(disk_name);
    host_regex = make_exact_host_expression(dump_hostname);

    clean_datestamp = g_strdup(dump_datestamp);
    for(ch=ch1=clean_datestamp;*ch1 != '\0';ch1++) {
	if(*ch1 != '-') {
	    *ch = *ch1;
	    ch++;
	}
    }
    *ch = '\0';
    /* push our feature list off to the tape server */
    /* XXX assumes that index server and tape server are equivalent, ew */

    if(am_has_feature(indexsrv_features, fe_amidxtaped_exchange_features)){
	g_free(tt);
	tt = g_strconcat("FEATURES=", our_features_string, NULL);
	send_to_tape_server(amidxtaped_streams[CTLFD].fd, tt);
	get_amidxtaped_line();
	if (!amidxtaped_line) {
	    g_fprintf(stderr, _("amrecover - amidxtaped closed the connection\n"));
	    stop_amidxtaped();
	    amfree(disk_regex);
	    amfree(host_regex);
	    amfree(clean_datestamp);
	    return -1;
	} else 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;
	}
    } else {
	*tapesrv_features = *indexsrv_features;
    }


    if(am_has_feature(indexsrv_features, fe_amidxtaped_header) &&
       am_has_feature(indexsrv_features, fe_amidxtaped_device) &&
       am_has_feature(indexsrv_features, fe_amidxtaped_host) &&
       am_has_feature(indexsrv_features, fe_amidxtaped_disk) &&
       am_has_feature(indexsrv_features, fe_amidxtaped_datestamp)) {

	if(am_has_feature(indexsrv_features, fe_amidxtaped_config)) {
	    g_free(tt);
	    tt = g_strconcat("CONFIG=", get_config_name(), NULL);
	    send_to_tape_server(amidxtaped_streams[CTLFD].fd, tt);
	}
	if(am_has_feature(indexsrv_features, fe_amidxtaped_label) && label &&
	   label[0] != '/' && strncmp(label, "HOLDING:/", 9) != 0) {
	    g_free(tt);
	    tt = g_strconcat("LABEL=", label, NULL);
	    send_to_tape_server(amidxtaped_streams[CTLFD].fd, tt);
	}
	if(am_has_feature(indexsrv_features, fe_amidxtaped_fsf)) {
	    char v_fsf[100];
	    g_snprintf(v_fsf, 99, "%lld", (long long)fsf);
	    g_free(tt);
	    tt = g_strconcat("FSF=", v_fsf, NULL);
	    send_to_tape_server(amidxtaped_streams[CTLFD].fd, tt);
	}
	send_to_tape_server(amidxtaped_streams[CTLFD].fd, "HEADER");
	g_free(tt);
	tt = g_strconcat("DEVICE=", dump_device_name, NULL);
	send_to_tape_server(amidxtaped_streams[CTLFD].fd, tt);
	g_free(tt);
	tt = g_strconcat("HOST=", host_regex, NULL);
	send_to_tape_server(amidxtaped_streams[CTLFD].fd, tt);
	g_free(tt);
	tt = g_strconcat("DISK=", disk_regex, NULL);
	send_to_tape_server(amidxtaped_streams[CTLFD].fd, tt);
	g_free(tt);
	tt = g_strconcat("DATESTAMP=", clean_datestamp, NULL);
	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)) {
	/* send to the tape server what tape file we want */
	/* 6 args:
	 *   "-h"
	 *   "-p"
	 *   "tape device"
	 *   "hostname"
	 *   "diskname"
	 *   "datestamp"
	 */
	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 0;
}


/*
 * Reads the first block of a tape file.
 */

void
read_file_header(
    char *	buffer,
    dumpfile_t *file,
    size_t	buflen,
    int		tapedev)
{
    ssize_t bytes_read;
    bytes_read = read_buffer(tapedev, buffer, buflen, READ_TIMEOUT);
    if(bytes_read < 0) {
	error(_("error reading header (%s), check amidxtaped.*.debug on server"),
	      strerror(errno));
	/*NOTREACHED*/
    }

    if((size_t)bytes_read < buflen) {
	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*/
    }

    /* bytes_read == buflen */
    parse_file_header(buffer, file, (size_t)bytes_read);
}

enum dumptypes {
	IS_UNKNOWN,
	IS_DUMP,
	IS_GNUTAR,
	IS_TAR,
#ifdef SAMBA_CLIENT
	IS_SAMBA,
#endif
	IS_SAMBA_TAR,
	IS_APPLICATION_API
};

static void
extract_files_child(
    ctl_data_t         *ctl_data)
{
    int save_errno;
    int   i;
    guint j;
    GPtrArray *argv_ptr = g_ptr_array_new();
    int files_off_tape;
    EXTRACT_LIST_ITEM *fn;
    enum dumptypes dumptype = IS_UNKNOWN;
    size_t len_program;
    char *cmd = NULL;
    guint passwd_field = 999999999;
#ifdef SAMBA_CLIENT
    char *domain = NULL, *smbpass = NULL;
#endif

    /* code executed by child to do extraction */
    /* never returns */

    /* make in_fd be our stdin */
    if (dup2(ctl_data->child_in[0], STDIN_FILENO) == -1)
    {
	error(_("dup2 failed in extract_files_child: %s"), strerror(errno));
	/*NOTREACHED*/
    }

    /* make out_fd be our stdout */
    if (dup2(ctl_data->child_out[1], STDOUT_FILENO) == -1)
    {
	error(_("dup2 failed in extract_files_child: %s"), strerror(errno));
	/*NOTREACHED*/
    }

    /* make err_fd be our stdout */
    if (dup2(ctl_data->child_err[1], STDERR_FILENO) == -1)
    {
	error(_("dup2 failed in extract_files_child: %s"), strerror(errno));
	/*NOTREACHED*/
    }

    if(ctl_data->file.type != F_DUMPFILE) {
	dump_dumpfile_t(&ctl_data->file);
	error(_("bad header"));
	/*NOTREACHED*/
    }

    if (ctl_data->file.program[0] != '\0') {
	if (g_str_equal(ctl_data->file.program, "APPLICATION"))
	    dumptype = IS_APPLICATION_API;
#ifdef GNUTAR
	if (g_str_equal(ctl_data->file.program, GNUTAR))
	    dumptype = IS_GNUTAR;
#endif

	if (dumptype == IS_UNKNOWN) {
	    len_program = strlen(ctl_data->file.program);
	    if(len_program >= 3 &&
	       g_str_equal(&ctl_data->file.program[len_program - 3], "tar"))
		dumptype = IS_TAR;
	}

#ifdef SAMBA_CLIENT
	if (dumptype == IS_UNKNOWN && g_str_equal(ctl_data->file.program,
                                                  SAMBA_CLIENT)) {
	    if (samba_extract_method == SAMBA_TAR)
	      dumptype = IS_SAMBA_TAR;
	    else
	      dumptype = IS_SAMBA;
	}
#endif
    }

    /* form the arguments to restore */
    files_off_tape = length_of_tape_list(ctl_data->elist);
    switch(dumptype) {
#ifdef SAMBA_CLIENT
    case IS_SAMBA:
	g_ptr_array_add(argv_ptr, g_strdup("smbclient"));
	smbpass = findpass(ctl_data->file.disk, &domain);
	if (smbpass) {
	    g_ptr_array_add(argv_ptr, g_strdup(ctl_data->file.disk));
	    g_ptr_array_add(argv_ptr, g_strdup("-U"));
	    passwd_field = argv_ptr->len;
	    g_ptr_array_add(argv_ptr, g_strdup(smbpass));
	    if (domain) {
		g_ptr_array_add(argv_ptr, g_strdup("-W"));
		g_ptr_array_add(argv_ptr, g_strdup(domain));
	    }
	}
	g_ptr_array_add(argv_ptr, g_strdup("-d0"));
	g_ptr_array_add(argv_ptr, g_strdup("-Tx"));
	g_ptr_array_add(argv_ptr, g_strdup("-"));	/* data on stdin */
	break;
#endif
    case IS_TAR:
    case IS_GNUTAR:
	g_ptr_array_add(argv_ptr, g_strdup("tar"));
	/* ignore trailing zero blocks on input (this was the default until tar-1.21) */
	g_ptr_array_add(argv_ptr, g_strdup("--ignore-zeros"));
	g_ptr_array_add(argv_ptr, g_strdup("--numeric-owner"));
	g_ptr_array_add(argv_ptr, g_strdup("-xpGvf"));
	g_ptr_array_add(argv_ptr, g_strdup("-"));	/* data on stdin */
	break;
    case IS_SAMBA_TAR:
	g_ptr_array_add(argv_ptr, g_strdup("tar"));
	g_ptr_array_add(argv_ptr, g_strdup("-xpvf"));
	g_ptr_array_add(argv_ptr, g_strdup("-"));	/* data on stdin */
	break;
    case IS_UNKNOWN:
    case IS_DUMP:
	g_ptr_array_add(argv_ptr, g_strdup("restore"));
#ifdef AIX_BACKUP
	g_ptr_array_add(argv_ptr, g_strdup("-xB"));
#else
#if defined(XFSDUMP)
	if (g_str_equal(ctl_data->file.program, XFSDUMP)) {
	    g_ptr_array_add(argv_ptr, g_strdup("-v"));
	    g_ptr_array_add(argv_ptr, g_strdup("silent"));
	} else
#endif
#if defined(VDUMP)
	if (g_str_equal(ctl_data->file.program, VDUMP)) {
	    g_ptr_array_add(argv_ptr, g_strdup("xf"));
	    g_ptr_array_add(argv_ptr, g_strdup("-"));	/* data on stdin */
	} else
#endif
	{
	g_ptr_array_add(argv_ptr, g_strdup("xbf"));
	g_ptr_array_add(argv_ptr, g_strdup("2")); /* read in units of 1K */
	g_ptr_array_add(argv_ptr, g_strdup("-"));	/* data on stdin */
	}
#endif
	break;
    case IS_APPLICATION_API:
	g_ptr_array_add(argv_ptr, g_strdup(ctl_data->file.application));
	g_ptr_array_add(argv_ptr, g_strdup("restore"));
	g_ptr_array_add(argv_ptr, g_strdup("--config"));
	g_ptr_array_add(argv_ptr, g_strdup(get_config_name()));
	g_ptr_array_add(argv_ptr, g_strdup("--disk"));
	g_ptr_array_add(argv_ptr, g_strdup(ctl_data->file.disk));
	if (dump_dle && dump_dle->device) {
	    g_ptr_array_add(argv_ptr, g_strdup("--device"));
	    g_ptr_array_add(argv_ptr, g_strdup(dump_dle->device));
	}
	if (ctl_data->data_path == DATA_PATH_DIRECTTCP) {
	    g_ptr_array_add(argv_ptr, g_strdup("--data-path"));
	    g_ptr_array_add(argv_ptr, g_strdup("DIRECTTCP"));
	    g_ptr_array_add(argv_ptr, g_strdup("--direct-tcp"));
	    g_ptr_array_add(argv_ptr, g_strdup(ctl_data->addrs));
	}
	if (ctl_data->bsu && ctl_data->bsu->smb_recover_mode &&
	    samba_extract_method == SAMBA_SMBCLIENT){
	    g_ptr_array_add(argv_ptr, g_strdup("--recover-mode"));
	    g_ptr_array_add(argv_ptr, g_strdup("smb"));
	}
	if (ctl_data->bsu && ctl_data->bsu->recover_dump_state_file &&
	    state_filename) {
	    if (ctl_data->bsu->dar) {
		g_ptr_array_add(argv_ptr, g_strdup("--dar"));
		g_ptr_array_add(argv_ptr, g_strdup("YES"));
		g_ptr_array_add(argv_ptr, g_strdup("--state-stream"));
		g_ptr_array_add(argv_ptr, g_strdup_printf("%d", ctl_data->dar_pipe[1]));
	    }
	    g_ptr_array_add(argv_ptr, g_strdup("--recover-dump-state-file"));
	    g_ptr_array_add(argv_ptr, g_strdup(state_filename));
	}
	g_ptr_array_add(argv_ptr, g_strdup("--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, NULL, dump_dle->application_property,
			     proplist, 0);
	    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 = 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:
#ifdef SAMBA_CLIENT
	case IS_SAMBA:
#endif
	    if (g_str_equal(fn->path, "/"))
		g_ptr_array_add(argv_ptr, g_strdup("."));
	    else
		g_ptr_array_add(argv_ptr, g_strconcat(".", fn->path, NULL));
	    break;
	case IS_UNKNOWN:
	case IS_DUMP:
#if defined(XFSDUMP)
	    if (g_str_equal(ctl_data->file.program, XFSDUMP)) {
		/*
		 * xfsrestore needs a -s option before each file to be
		 * restored, and also wants them to be relative paths.
		 */
		g_ptr_array_add(argv_ptr, g_strdup("-s"));
		g_ptr_array_add(argv_ptr, g_strdup(fn->path + 1));
	    } else
#endif
	    {
	    g_ptr_array_add(argv_ptr, g_strdup(fn->path));
	    }
	    break;
	}
    }
#if defined(XFSDUMP)
    if (g_str_equal(ctl_data->file.program, XFSDUMP)) {
	g_ptr_array_add(argv_ptr, g_strdup("-"));
	g_ptr_array_add(argv_ptr, g_strdup("."));
    }
#endif
    g_ptr_array_add(argv_ptr, NULL);

    switch (dumptype) {
#ifdef SAMBA_CLIENT
    case IS_SAMBA:
	cmd = g_strdup(SAMBA_CLIENT);
	break;
#else
	/* fall through to ... */
#endif
    case IS_TAR:
    case IS_GNUTAR:
    case IS_SAMBA_TAR:
#ifndef GNUTAR
	g_fprintf(stderr, _("warning: GNUTAR program not available.\n"));
	cmd = g_strdup("tar");
#else
  	cmd = g_strdup(GNUTAR);
#endif
    	break;
    case IS_UNKNOWN:
    case IS_DUMP:
	cmd = NULL;
#if defined(DUMP)
	if (g_str_equal(ctl_data->file.program, DUMP)) {
    	    cmd = g_strdup(RESTORE);
	}
#endif
#if defined(VDUMP)
	if (g_str_equal(ctl_data->file.program, VDUMP)) {
    	    cmd = g_strdup(VRESTORE);
	}
#endif
#if defined(VXDUMP)
	if (g_str_equal(ctl_data->file.program, VXDUMP)) {
    	    cmd = g_strdup(VXRESTORE);
	}
#endif
#if defined(XFSDUMP)
	if (g_str_equal(ctl_data->file.program, XFSDUMP)) {
    	    cmd = g_strdup(XFSRESTORE);
	}
#endif
	if (cmd == NULL) {
	    g_fprintf(stderr, _("warning: restore program for %s not available.\n"),
		    ctl_data->file.program);
	    cmd = g_strdup("restore");
	}
	break;
    case IS_APPLICATION_API:
	cmd = g_strjoin(NULL, APPLICATION_DIR, "/", ctl_data->file.application, NULL);
	break;
    }
    if (cmd) {
        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"), (char *)g_ptr_array_index(argv_ptr, j));
	}
	if (ctl_data->dar_pipe[1] >= 0) {
	    safe_fd2(-1, 3, ctl_data->dar_pipe[1]);
	} else {
	    safe_fd(-1, 3);
	}
        (void)execv(cmd, (char **)argv_ptr->pdata);
	/* only get here if exec failed */
	save_errno = errno;
	g_ptr_array_free_full(argv_ptr);
	errno = save_errno;
        perror(_("amrecover couldn't exec"));
        g_fprintf(stderr, _(" problem executing %s\n"), cmd);
	amfree(cmd);
    }
    exit(1);
    /*NOT REACHED */
}

/*
 * Interpose something between the process writing out the dump (writing it to
 * some extraction program, really) and the socket from which we're reading, so
 * that we can do things like prompt for human interaction for multiple tapes.
 */
int
writer_intermediary(
    EXTRACT_LIST *	elist)
{
    amwait_t    extractor_status;

    data_path_set = DATA_PATH_AMANDA;
    got_use_dar           = 0;
    send_use_dar           = 0;
    ctl_state.bytes_read  = 0;
    ctl_state.fd          = -1;
    ctl_state.state_done  = FALSE;

    ctl_data.header_done  = FALSE;
    ctl_data.child_in[0]  = -1;
    ctl_data.child_in[1]  = -1;
    ctl_data.child_out[0] = -1;
    ctl_data.child_out[1] = -1;
    ctl_data.child_err[0] = -1;
    ctl_data.child_err[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;

    header_size = 0;
    header_send_size = DISK_BLOCK_BYTES;
    crc32_init(&crc_in);
    crc32_init(&native_crc.crc);

    if (amidxtaped_streams[STATEFD].fd != NULL) {
	security_stream_read(amidxtaped_streams[STATEFD].fd,
			 read_amidxtaped_state, &ctl_state);
    } else {
	ctl_state.state_done = TRUE;
    }

    if (!am_has_feature(tapesrv_features, fe_amrecover_header_send_size)) {
	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 = g_strconcat("TAPE ", tape_device_name, NULL);
		    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;
	    }
	} else if (strncmp_const(amidxtaped_line, "USE-DAR ") == 0) {
	    if (strncmp_const(amidxtaped_line+8, "YES") == 0) {
		use_dar = 1;
	    } else {
		use_dar = 0;
	    }
	    got_use_dar = 1;
	    try_send_use_dar();
	} 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 = g_strdup(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) {
	    if (last_is_size) {
		g_printf("\n");
		last_is_size = FALSE;
	    }
	    g_printf("%s\n",&amidxtaped_line[8]);
	} else if(strncmp_const(amidxtaped_line, "DATA-STATUS ") == 0) {
	    //g_printf("status: %s\n",&amidxtaped_line[12]);
	} else if(strncmp_const(amidxtaped_line, "DATA-CRC ") == 0) {
	    parse_crc(&amidxtaped_line[9], &network_crc);
	} else if(strncmp_const(amidxtaped_line, "HEADER-SEND-SIZE ") == 0) {
	    header_send_size = atoi(amidxtaped_line+17);
	    security_stream_read(amidxtaped_streams[DATAFD].fd,
			         read_amidxtaped_data, &ctl_data);
	    if (am_has_feature(tapesrv_features, fe_amrecover_header_ready)) {
		send_to_tape_server(amidxtaped_streams[CTLFD].fd, "HEADER-READY");
	    }
	} else if(strncmp_const(amidxtaped_line, "STATE-SEND") == 0) {
	    // We are already waiting for the state file
	    if (am_has_feature(tapesrv_features, fe_amrecover_state_ready)) {
		send_to_tape_server(amidxtaped_streams[CTLFD].fd, "STATE-READY");
	    }
	} else if(strncmp_const(amidxtaped_line, "DATA-SEND") == 0) {
	    // We are already waiting for the data
	    if (am_has_feature(tapesrv_features, fe_amrecover_data_ready)) {
		send_to_tape_server(amidxtaped_streams[CTLFD].fd, "DATA-READY");
	    }
	} else {
	    g_fprintf(stderr, _("Strange message from tape server: %s\n"),
		    amidxtaped_line);
	    break;
	}
    }

    /* CTL might be close before DATA */
    event_loop(0);

    if (native_crc.thread) {
	g_thread_join(native_crc.thread);
    }

    if (!ctl_data.file.encrypted && !ctl_data.file.compressed) {
	native_crc.crc.crc = crc_in.crc;
	native_crc.crc.size = crc_in.size;
    }
    g_debug("native_crc: %08x:%lld", ctl_data.file.native_crc.crc, (long long)ctl_data.file.native_crc.size);
    g_debug("client_crc: %08x:%lld", ctl_data.file.client_crc.crc, (long long)ctl_data.file.client_crc.size);
    g_debug("server_crc: %08x:%lld", ctl_data.file.server_crc.crc, (long long)ctl_data.file.server_crc.size);
    g_debug("crc_in    : %08x:%lld", crc32_finish(&crc_in), (long long)crc_in.size);
    g_debug("crc_native: %08x:%lld", crc32_finish(&native_crc.crc), (long long)native_crc.crc.size);

    if (network_crc.crc > 0 && network_crc.crc != crc32_finish(&crc_in) &&
	network_crc.size == crc_in.size) {
	g_fprintf(stderr,
		"Network-crc (%08x:%lld) and data-in-crc (%08x:%lld) differ\n",
		network_crc.crc, (long long)network_crc.size,
		crc32_finish(&crc_in), (long long)crc_in.size);
    }

    if (ctl_data.file.native_crc.crc > 0 &&
	ctl_data.file.native_crc.crc != crc32_finish(&native_crc.crc) &&
	ctl_data.file.native_crc.size == native_crc.crc.size) {
	g_fprintf(stderr,
		"dump-native-crc (%08x:%lld) and native-crc (%08x:%lld) differ\n",
		ctl_data.file.native_crc.crc,
		(long long)ctl_data.file.native_crc.size,
		crc32_finish(&native_crc.crc), (long long)native_crc.crc.size);
    }
    dumpfile_free_data(&ctl_data.file);
    amfree(ctl_data.addrs);
    amfree(ctl_data.bsu);
    if (ctl_data.child_in[1] != -1)
	aclose(ctl_data.child_in[1]);
    if (ctl_data.child_out[0] != -1)
	aclose(ctl_data.child_out[0]);
    if (ctl_data.child_err[0] != -1)
	aclose(ctl_data.child_err[0]);

    if (!ctl_data.header_done) {
	g_printf(_("Got no header and data from server, check in amidxtaped.*.debug and amandad.*.debug files on server\n"));
    }

    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(void)
{
    EXTRACT_LIST *elist;
    char *l;
    int first;
    int otc;
    tapelist_t *tlist = NULL, *a_tlist;
    g_option_t g_options;
    levellist_t all_level = NULL;
    int last_level;
    char *etapelist;

    if (!is_extract_list_nonempty())
    {
	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) {
	g_free(tape_server_name);
	tape_server_name = g_strdup(server_name);
    }
    if (tape_device_name == NULL) {
	if (send_command("TAPE") == -1)
	    exit(1);
	if (get_reply_line() == -1)
	    exit(1);
	l = reply_line();
	if (!server_happy())
	{
	    g_printf("%s\n", l);
	    exit(1);
	}
	/* skip reply number */
	g_free(tape_device_name);
	tape_device_name = g_strdup(l + 4);
    }

    if (g_str_equal(tape_device_name, "/dev/null"))
    {
	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)) {
	if (elist->tape[0] != '/' && strncmp(elist->tape, "HOLDING:/",9) != 0) {
	    if(first) {
		g_printf(_("\nExtracting files using tape drive %s on host %s.\n"),
			tape_device_name, tape_server_name);
		g_printf(_("The following tapes are needed:"));
		first=0;
	    }
	    else
		g_printf("                               ");
	    tlist = unmarshal_tapelist_str(elist->tape,
			am_has_feature(indexsrv_features,
				       fe_amrecover_storage_in_marshall));
	    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)) {
	if (elist->tape[0] == '/' || strncmp(elist->tape, "HOLDING:/",9) == 0) {
	    if(first) {
		g_printf(_("\nExtracting files from holding disk on host %s.\n"),
			tape_server_name);
		g_printf(_("The following files are needed:"));
		first=0;
	    }
	    else
		g_printf("                               ");
	    tlist = unmarshal_tapelist_str(elist->tape,
			am_has_feature(indexsrv_features,
				       fe_amrecover_storage_in_marshall));
	    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)) {
	am_level_t *level = g_new0(am_level_t, 1);
	level->level = elist->level;
	all_level = g_slist_append(all_level, level);
    }
    if (dump_dle) {
	slist_free_full(dump_dle->levellist, g_free);
	dump_dle->levellist = all_level;
	run_client_scripts(EXECUTE_ON_PRE_RECOVER, &g_options, dump_dle,
			   stderr, NULL);
	dump_dle->levellist = NULL;
    }
    last_level = -1;
    while ((elist = first_tape_list()) != NULL)
    {
	if (elist->tape[0] == '/' || strncmp(elist->tape, "HOLDING:/",9) == 0) {
	    g_free(dump_device_name);
	    if (elist->tape[0] == '/') {
		dump_device_name = g_strdup(elist->tape);
	    } else {
		dump_device_name = g_strdup(elist->tape+8);
	    }
	    g_printf(_("Extracting from file "));
	    tlist = unmarshal_tapelist_str(dump_device_name,
			am_has_feature(indexsrv_features,
				       fe_amrecover_storage_in_marshall));
	    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 {
	    g_printf(_("Extracting files using tape drive %s on host %s.\n"),
		   tape_device_name, tape_server_name);
	    tlist = unmarshal_tapelist_str(elist->tape,
			am_has_feature(indexsrv_features,
				       fe_amrecover_storage_in_marshall));
	    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;
	    else if (otc == SKIP_TAPE) {
		delete_tape_list(elist); /* skip this tape */
		continue;
	    }
	    g_free(dump_device_name);
	    dump_device_name = g_strdup(tape_device_name);
	}
	g_free(dump_datestamp);
	dump_datestamp = g_strdup(elist->date);

	if (last_level != -1 && dump_dle) {
	    am_level_t *level;

	    level = g_new0(am_level_t, 1);
	    level->level = last_level;
	    dump_dle->levellist = g_slist_append(dump_dle->levellist, level);

	    level = g_new0(am_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, NULL);
	    slist_free_full(dump_dle->levellist, g_free);
	    dump_dle->levellist = NULL;
	}

	if (am_has_feature(indexsrv_features, fe_amrecover_storage_in_marshall) &&
	    !am_has_feature(indexsrv_features, fe_amidxtaped_storage_in_marshall)) {
	    tlist = unmarshal_tapelist_str(elist->tape, 1);
	    etapelist = marshal_tapelist(tlist, 1, 7);
	    free_tapelist(tlist);
	} else if (!am_has_feature(indexsrv_features, fe_amidxtaped_storage_in_marshall) &&
	            am_has_feature(indexsrv_features, fe_amidxtaped_storage_in_marshall)) {
	    tlist = unmarshal_tapelist_str(elist->tape, 0);
	    for(a_tlist = tlist; a_tlist != NULL; a_tlist = a_tlist->next)
		a_tlist->storage = g_strdup(get_config_name());
	    etapelist = marshal_tapelist(tlist, 1, 0);
	    free_tapelist(tlist);
	} else {
	    etapelist = g_strdup(elist->tape);
	}
	/* connect to the tape handler daemon on the tape drive server */
	if ((extract_files_setup(etapelist, elist->fileno)) == -1)
	{
	    g_fprintf(stderr, _("amrecover - can't talk to tape server: %s\n"),
		    errstr);
	    g_free(etapelist);
	    return;
	}
	g_free(etapelist);

	if (dump_dle) {
	    am_level_t *level;

	    level = g_new0(am_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, NULL);
	}
	last_level = elist->level;

	/* 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, NULL);
	    slist_free_full(dump_dle->levellist, g_free);
	    dump_dle->levellist = NULL;
	}
    }
    if (dump_dle) {
	dump_dle->levellist = all_level;
	run_client_scripts(EXECUTE_ON_POST_RECOVER, &g_options, dump_dle,
			   stderr, NULL);
	slist_free_full(dump_dle->levellist, g_free);
	all_level = NULL;
	dump_dle->levellist = NULL;
    }
}

void
try_send_use_dar(void)
{
    if (am_has_feature(tapesrv_features, fe_amidxtaped_dar)) {
	if (!send_use_dar && got_use_dar && ctl_data.header_done && ctl_state.state_done) {
	    if (use_dar && ctl_data.bsu && ctl_data.bsu->dar && state_filename) {
		send_to_tape_server(amidxtaped_streams[CTLFD].fd, "USE-DAR YES");
	    } else {
		send_to_tape_server(amidxtaped_streams[CTLFD].fd, "USE-DAR NO");
		if (state_filename) {
		    unlink(state_filename);
		    amfree(state_filename);
		}
	    }
	    send_use_dar = 1;
	}
    } else {
	send_use_dar = 1;
    }

    if (send_use_dar && ctl_data.header_done && ctl_state.state_done) {
	if (am_has_feature(tapesrv_features, fe_amidxtaped_datapath)) {
	    /* send DATA-PATH request */
	    char *msg = g_strdup_printf("AVAIL-DATAPATH%s%s",
			   (data_path_set & DATA_PATH_AMANDA) ? " AMANDA" : "",
			   (data_path_set & DATA_PATH_DIRECTTCP) ? " DIRECT-TCP" : "");
	    send_to_tape_server(amidxtaped_streams[CTLFD].fd, msg);
	    g_free(msg);
	}
    }
}

static void
amidxtaped_response(
    void *		datap,
    pkt_t *		pkt,
    security_handle_t *	sech)
{
    int ports[NSTREAMS], *response_error = datap;
    guint i;
    char *p;
    char *tok;
    char *extra = NULL;

    assert(response_error != NULL);
    assert(sech != NULL);
    memset(ports, -1, sizeof(ports));

    if (pkt == NULL) {
	g_free(errstr);
	errstr = g_strdup_printf(_("[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 || !g_str_equal(tok, "ERROR"))
	    goto bad_nak;

	tok = strtok(NULL, "\n");
	if (tok != NULL) {
	    g_free(errstr);
	    errstr = g_strconcat("NAK: ", tok, NULL);
	    *response_error = 1;
	} else {
bad_nak:
	    g_free(errstr);
	    errstr = g_strdup("request NAK");
	    *response_error = 2;
	}
	return;
    }

    if (pkt->type != P_REP) {
	g_free(errstr);
	errstr = g_strdup_printf(_("received strange packet type %s: %s"),
                                 pkt_type2str(pkt->type), pkt->body);
	*response_error = 1;
	return;
    }

#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 (g_str_equal(tok, "ERROR")) {
	    tok = strtok(NULL, "\n");
	    if (tok == NULL)
		tok = _("[bogus error packet]");
	    g_free(errstr);
	    errstr = g_strdup(tok);
	    *response_error = 2;
	    return;
	}


        /*
         * Regular packets have CONNECT followed by 2 or 3 streams
         */
        if (g_str_equal(tok, "CONNECT")) {
	    /*
	     * Parse the 2 or 3 stream specifiers out of the packet.
	     */
	    for (i = 0; i < NSTREAMS; i++) {
		tok = strtok(NULL, " ");
		if (tok != NULL && strcmp(tok, "\n") == 0) break;
		if (tok == NULL || !g_str_equal(tok,
                                                amidxtaped_streams[i].name)) {
		    extra = g_strdup_printf(_("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 = g_strdup_printf(_("CONNECT %s token is \"%s\": expected a port number"),
				      amidxtaped_streams[i].name,
				      tok ? tok : "(null)");
		    goto parse_error;
		}
	    }
	    continue;
	}

	/*
	 * OPTIONS [options string] '\n'
	 */
	if (g_str_equal(tok, "OPTIONS")) {
	    tok = strtok(NULL, "\n");
	    if (tok == NULL) {
		extra = g_strdup(_("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;
	}
/*
	extra = g_strdup_printf("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) {
            g_free(errstr);
            errstr = g_strdup_printf(_("[could not connect %s stream: %s]"),
                                     amidxtaped_streams[i].name, security_geterror(sech));
	    goto connect_error;
	}
    }
    /*
     * 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) {
            g_free(errstr);
            errstr = g_strdup_printf(_("[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) {
        g_free(errstr);
        errstr = g_strdup("[couldn't open CTL streams]");
        goto connect_error;
    }
    if (amidxtaped_streams[DATAFD].fd == NULL) {
        g_free(errstr);
        errstr = g_strdup("[couldn't open DATA streams]");
        goto connect_error;
    }

    /* everything worked */
    *response_error = 0;
    return;

parse_error:
    if (extra) {
	g_free(errstr);
	errstr = g_strdup_printf(_("[parse of reply message failed: %s]"),
                                 extra);
    } else {
	g_free(errstr);
	errstr = g_strdup("[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)
{
    guint 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;
        }
    }

    if (stderr_isatty) {
	fprintf(stderr, "\n");
    }
}

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 = g_strdup("");

    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 = g_malloc(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 = g_strdup(s+2);
    amidxtaped_line = g_strdup(ctl_buffer);
    amfree(ctl_buffer);
    ctl_buffer = newbuf;
    return 0;
}

static void
handle_child_out(
    void *cookie)
{
    cdata_t  *cdata = cookie;
    ssize_t   nread;
    char     *b, *p;
    gint64    len;

    if (cdata->buffer == NULL) {
	/* allocate initial buffer */
	cdata->buffer = g_malloc(2048);
	cdata->first = 0;
	cdata->size = 0;
	cdata->allocated_size = 2048;
    } else if (cdata->first > 0) {
	if (cdata->allocated_size - cdata->size - cdata->first < 1024) {
	    memmove(cdata->buffer, cdata->buffer + cdata->first,
	                            cdata->size);
	    cdata->first = 0;
	}
    } else if (cdata->allocated_size - cdata->size < 1024) {
	/* double the size of the buffer */
	cdata->allocated_size *= 2;
	cdata->buffer = g_realloc(cdata->buffer, cdata->allocated_size);
    }

    nread = read(cdata->fd, cdata->buffer + cdata->first + cdata->size,
		 cdata->allocated_size - cdata->first - cdata->size - 2);

    if (nread <= 0) {
	event_release(cdata->event);
	aclose(cdata->fd);
	if (cdata->size > 0 && cdata->buffer[cdata->first + cdata->size - 1] != '\n') {
	    /* Add a '\n' at end of buffer */
	    cdata->buffer[cdata->first + cdata->size] = '\n';
	    cdata->size++;
	}
    } else {
	cdata->size += nread;
    }

    /* process all complete lines */
    b = cdata->buffer + cdata->first;
    b[cdata->size] = '\0';
    while (b < cdata->buffer + cdata->first + cdata->size &&
           (p = strchr(b, '\n')) != NULL) {
        *p = '\0';
	if (last_is_size)
	    g_fprintf(cdata->output, "\r");
	if (cdata->name) {
            g_fprintf(cdata->output, "%s: %s\n", cdata->name, b);
	} else {
            g_fprintf(cdata->output, "%s\n", b);
	}
	last_is_size = FALSE;
        len = p - b + 1;
        cdata->first += len;
        cdata->size -= len;
        b = p + 1;
    }

    if (nread <= 0) {
        g_free(cdata->name);
        g_free(cdata->buffer);
    }
}

static void
handle_dar_command(
    void *cookie)
{
    cdata_t  *cdata = cookie;
    ssize_t   nread;
    char     *b, *p;
    gint64    len;

    if (cdata->buffer == NULL) {
	/* allocate initial buffer */
	cdata->buffer = g_malloc(2048);
	cdata->first = 0;
	cdata->size = 0;
	cdata->allocated_size = 2048;
    } else if (cdata->first > 0) {
	if (cdata->allocated_size - cdata->size - cdata->first < 1024) {
	    memmove(cdata->buffer, cdata->buffer + cdata->first,
	                            cdata->size);
	    cdata->first = 0;
	}
    } else if (cdata->allocated_size - cdata->size < 1024) {
	/* double the size of the buffer */
	cdata->allocated_size *= 2;
	cdata->buffer = g_realloc(cdata->buffer, cdata->allocated_size);
    }

    nread = read(cdata->fd, cdata->buffer + cdata->first + cdata->size,
		 cdata->allocated_size - cdata->first - cdata->size - 2);

    if (nread <= 0) {
	event_release(cdata->event);
	aclose(cdata->fd);
	if (cdata->size > 0 && cdata->buffer[cdata->first + cdata->size - 1] != '\n') {
	    /* Add a '\n' at end of buffer */
	    cdata->buffer[cdata->first + cdata->size] = '\n';
	    cdata->size++;
	}
    } else {
	cdata->size += nread;
    }

    /* process all complete lines */
    b = cdata->buffer + cdata->first;
    b[cdata->size] = '\0';
    while (b < cdata->buffer + cdata->first + cdata->size &&
           (p = strchr(b, '\n')) != NULL) {
        *p = '\0';
	g_debug("dar: %s", b);
	send_to_tape_server(amidxtaped_streams[CTLFD].fd, b);
        len = p - b + 1;
        cdata->first += len;
        cdata->size -= len;
        b = p + 1;
    }

    if (nread <= 0) {
        g_free(cdata->name);
        g_free(cdata->buffer);
	send_to_tape_server(amidxtaped_streams[CTLFD].fd, "DAR-DONE");
    }
}


int fd_state = -1;
off_t size_of_state = 0;

static void
read_amidxtaped_state(
    void *	cookie,
    void *	buf,
    ssize_t	size)
{
    ctl_state_t *ctl_state = (ctl_state_t *)cookie;
    assert(cookie != NULL);

    if (size < 0) {
	g_free(errstr);
	errstr = g_strconcat(_("amidxtaped read: "),
                             security_stream_geterror(amidxtaped_streams[STATEFD].fd),
                             NULL);
	return;
    }

    /*
     * EOF.  Stop and return.
     */
    if (size == 0) {
	security_stream_close(amidxtaped_streams[STATEFD].fd);
	amidxtaped_streams[STATEFD].fd = NULL;

	aclose(ctl_state->fd);
	if (ctl_state->bytes_read == 0) {
	    if (state_filename) {
		unlink(state_filename);
		g_free(state_filename);
		state_filename = NULL;
	    }
	}
	ctl_state->state_done = TRUE;
	if (am_has_feature(tapesrv_features, fe_amrecover_state_done)) {
	    send_to_tape_server(amidxtaped_streams[CTLFD].fd, "STATE-DONE");
	}
	try_send_use_dar();
	return;
    }

    assert(buf != NULL);

    if (ctl_state->fd == -1) {
	char *host = sanitise_filename(dump_hostname);
	char *disk = sanitise_filename(disk_name);
	struct passwd *pwd;
	state_filename = g_strdup_printf("%s/%s.%s.%s.state",
					 AMANDA_TMPDIR,
					 host, disk, dump_datestamp);
	if ((pwd = getpwnam(CLIENT_LOGIN)) == NULL) {
	    g_debug("Failed to getpwnam(%s): %s", CLIENT_LOGIN,
		    strerror(errno));
	}

	g_debug("state_filename: %s", state_filename);
	g_free(host);
	g_free(disk);
	unlink(state_filename);

	if ((ctl_state->fd = open(state_filename, O_WRONLY | O_CREAT | O_EXCL, 0600)) == -1) {
	    g_debug("Failed to open the state file '%s': %s", state_filename, strerror(errno));
	    g_free(state_filename);
	    state_filename = NULL;
	    return;
	}
	if (pwd && fchown(ctl_state->fd, pwd->pw_uid, pwd->pw_gid) == -1) {
	    g_debug("Failed to fchown '%s': %s", state_filename,
		    strerror(errno));
	}
    }

    full_write(ctl_state->fd, buf, size);
    ctl_state->bytes_read += size;
}

typedef struct data_cookie_t {
    char    *buf;
    size_t   size;
    size_t   alloc_size;
    size_t   count;
    event_handle_t *event;
    ctl_data_t     *ctl_data;
} data_cookie_t;
data_cookie_t data_cookie = { NULL, 0, 0, 0, NULL, NULL };

static void write_data_to_app(void *);

static void
read_amidxtaped_data(
    void *	cookie,
    void *	buf,
    ssize_t	size)
{
    ssize_t count;

    ctl_data_t *ctl_data = (ctl_data_t *)cookie;
    assert(cookie != NULL);

    if (size < 0) {
	g_free(errstr);
	errstr = g_strconcat(_("amidxtaped read: "),
                             security_stream_geterror(amidxtaped_streams[DATAFD].fd),
                             NULL);
	return;
    }

    /*
     * EOF.  Stop and return.
     */
    if (size == 0) {
	if (stderr_isatty) {
	    if (last_is_size) {
		fprintf(stderr, "\r%lld kb ",
			    (long long)ctl_data->bytes_read/1024);
	    } else {
		fprintf(stderr, "%lld kb ",
			    (long long)ctl_data->bytes_read/1024);
		last_is_size = TRUE;
	    }
	} else {
	    fprintf(stderr, "%lld kb\n",
			(long long)ctl_data->bytes_read/1024);
	}
	security_stream_close(amidxtaped_streams[DATAFD].fd);
	amidxtaped_streams[DATAFD].fd = NULL;
	aclose(ctl_data->child_in[1]);
	//if (am_has_feature(tapesrv_features, fe_amrecover_data_done)) {
	//    send_to_tape_server(amidxtaped_streams[CTLFD].fd, "DATA-DONE");
	//}
	/*
	 * If the mesg fd has also shut down, then we're done.
	 */
	return;
    }

    assert(buf != NULL);

    if (!ctl_data->header_done) {
	GPtrArray  *errarray;
	int to_move;

	to_move = MIN(header_send_size-header_size, size);
	memcpy(header_buf+header_size, buf, to_move);
	header_size += to_move;

	g_debug("read header %zd => %d (%d)", size, header_size, header_send_size);
	if (header_size < header_send_size) {
	    /* wait to read more data */
	    return;
	} else if (header_size > header_send_size) {
	    error("header_size is %d\n", header_size);
	}
	assert (to_move == size);
	security_stream_read_cancel(amidxtaped_streams[DATAFD].fd);
	/* parse the file header */
	fh_init(&ctl_data->file);
	parse_file_header(header_buf, &ctl_data->file, (size_t)header_size);

	/* call backup_support_option */
	if (g_str_equal(ctl_data->file.program, "APPLICATION")) {
	    ctl_data->bsu = backup_support_option(ctl_data->file.application,
						  &errarray);
	    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;
	}
	/* handle backup_support_option failure */

	ctl_data->header_done = TRUE;
	if (ctl_data->file.dle_str) {
	    if (dump_dle) free_dle(dump_dle);
	    dump_dle = amxml_parse_node_CHAR(ctl_data->file.dle_str, NULL);
	}

	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_amrecover_header_done)) {
	    send_to_tape_server(amidxtaped_streams[CTLFD].fd, "HEADER-DONE");
	}

	try_send_use_dar();

	if (!am_has_feature(tapesrv_features, fe_amidxtaped_datapath)) {
	    start_processing_data(ctl_data);
	}
    } else {
	/* print every second */
	time_t current_time = time(NULL);
	ctl_data->bytes_read += size;
	if (current_time > last_time) {
	    last_time = current_time;

	    if (stderr_isatty) {
		if (last_is_size) {
		    fprintf(stderr, "\r%lld kb ",
			    (long long)ctl_data->bytes_read/1024);
		} else {
		    fprintf(stderr, "%lld kb ",
			    (long long)ctl_data->bytes_read/1024);
		    last_is_size = TRUE;
		}
	    } else {
		fprintf(stderr, "%lld kb\n",
			(long long)ctl_data->bytes_read/1024);
	    }
	}

	/* Only the data is sent to the child */
	count = write(ctl_data->child_in[1], buf, (size_t)size);
	if (count > 0) {
	    crc32_add((uint8_t *)buf, count, &crc_in);
	}
	if ((count >= 0 && count < size) || (count == -1 && errno == EAGAIN)) {
		if (count == -1) count = 0;
		security_stream_pause(amidxtaped_streams[DATAFD].fd);
		if (!data_cookie.buf || data_cookie.alloc_size < (size_t)size) {
		    if (data_cookie.buf) {
			g_free(data_cookie.buf);
		    }
		    data_cookie.buf = g_malloc(size);
		    data_cookie.alloc_size = size;
		}
		memcpy(data_cookie.buf, buf, size);
		data_cookie.size = size;
		data_cookie.count = count;
		data_cookie.ctl_data = ctl_data;
		data_cookie.event = event_create(ctl_data->child_in[1], EV_WRITEFD, &write_data_to_app, &data_cookie);
		event_activate(data_cookie.event);
	} else if (count == -1 && errno != EAGAIN) {
	    g_debug("A Failed to write to application: %s", strerror(errno));
	    g_printf("A Failed to write to application: %s\n", strerror(errno));
	    stop_amidxtaped();
	}
    }
}

static void
write_data_to_app(
    void *cookie)
{
    data_cookie_t *data_cookie = cookie;
    ssize_t count;

    count = write(data_cookie->ctl_data->child_in[1], data_cookie->buf+data_cookie->count, data_cookie->size-data_cookie->count);
    if (count > 0) {
	crc32_add((uint8_t *)data_cookie->buf+data_cookie->count, count, &crc_in);
	data_cookie->count += count;
    }
    if (data_cookie->count == data_cookie->size) {
	event_release(data_cookie->event);
	data_cookie->event = NULL;
	security_stream_resume(amidxtaped_streams[DATAFD].fd);
    } else if (count == -1 && errno != EAGAIN) {
	security_stream_resume(amidxtaped_streams[DATAFD].fd);
	g_debug("B Failed to write to application: %s", strerror(errno));
	g_printf("B Failed to write to application: %s\n", strerror(errno));
	stop_amidxtaped();
    }
}

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 (g_str_equal(ctl_data->file.program, "GNUTAR") ||
	    (g_str_equal(ctl_data->file.program, "APPLICATION") &&
	     g_str_equal(ctl_data->file.application, "amgtar"))) {
	    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_in) == -1) {
	error(_("extract_list - error setting up pipe to extractor: %s\n"),
	      strerror(errno));
	/*NOTREACHED*/
    }
    if (fcntl(ctl_data->child_in[1], F_SETFL, O_NONBLOCK) == -1) {
	error(_("extract_list - Can't set O_NONBLOCK: %s\n"),
	      strerror(errno));
	/*NOTREACHED*/
    }

    if (pipe(ctl_data->child_out) == -1) {
	error(_("extract_list - error setting up pipe to extractor: %s\n"),
	      strerror(errno));
	/*NOTREACHED*/
    }

    if (pipe(ctl_data->child_err) == -1) {
	error(_("extract_list - error setting up pipe to extractor: %s\n"),
	      strerror(errno));
	/*NOTREACHED*/
    }

    /* decrypt */
    ctl_data->decrypt_cdata.fd = -1;
    if (ctl_data->file.encrypted &&
	am_has_feature(tapesrv_features, fe_amrecover_receive_unfiltered)) {
	char *argv[3];
	int  crypt_out;
	int pipe_decrypt_err[2];

	if (pipe(pipe_decrypt_err) == -1) {
	    error(_("extract_list - error setting up pipe to extractor: %s\n"),
	          strerror(errno));
	    /*NOTREACHED*/
	}

	g_debug("image is encrypted %s %s", ctl_data->file.clnt_encrypt, ctl_data->file.clnt_decrypt_opt);
	argv[0] = ctl_data->file.clnt_encrypt;
	argv[1] = ctl_data->file.clnt_decrypt_opt;
	argv[2] = NULL;
	pipespawnv(ctl_data->file.clnt_encrypt, STDOUT_PIPE, 0,
		   &ctl_data->child_in[0], &crypt_out, &pipe_decrypt_err[1],
		   argv);
	ctl_data->child_in[0] = crypt_out;
	aclose(pipe_decrypt_err[1]);
	ctl_data->decrypt_cdata.fd = pipe_decrypt_err[0];
	ctl_data->decrypt_cdata.output = stderr;
	ctl_data->decrypt_cdata.name = g_strdup(ctl_data->file.clnt_encrypt);
	ctl_data->decrypt_cdata.buffer = NULL;
	ctl_data->decrypt_cdata.event = NULL;
    }

    /* decompress */
    ctl_data->decompress_cdata.fd = -1;
    if (ctl_data->file.compressed &&
	am_has_feature(tapesrv_features, fe_amrecover_receive_unfiltered)) {
	char *argv[3];
	int  comp_out;
	char *comp_prog;
	char *comp_arg;
	int pipe_decompress_err[2];

	if (pipe(pipe_decompress_err) == -1) {
	    error(_("extract_list - error setting up pipe to extractor: %s\n"),
	          strerror(errno));
	    /*NOTREACHED*/
	}

	g_debug("image is compressed %s", ctl_data->file.clntcompprog);
	if (strlen(ctl_data->file.clntcompprog) > 0) {
	    comp_prog = ctl_data->file.clntcompprog;
	    comp_arg = "-d";
	} else {
	    comp_prog = UNCOMPRESS_PATH;
	    comp_arg = UNCOMPRESS_OPT;
	}
	argv[0] = comp_prog;
	argv[1] = comp_arg;
	argv[2] = NULL;
	pipespawnv(comp_prog, STDOUT_PIPE, 0, &ctl_data->child_in[0],
		   &comp_out, &pipe_decompress_err[1], argv);
	ctl_data->child_in[0] = comp_out;
	aclose(pipe_decompress_err[1]);
	ctl_data->decrypt_cdata.fd = pipe_decompress_err[0];
	ctl_data->decrypt_cdata.output = stderr;
	ctl_data->decrypt_cdata.name = g_strdup(comp_prog);
	ctl_data->decrypt_cdata.buffer = NULL;
	ctl_data->decrypt_cdata.event = NULL;
    }

    /* compute native-crc */
    if (ctl_data->file.encrypted || ctl_data->file.compressed) {
	if (pipe(ctl_data->crc_pipe) == -1) {
	    error(_("extract_list - error setting up ctc pipe: %s\n"),
		  strerror(errno));
	    /*NOTREACHED*/
	}

	native_crc.in  = ctl_data->child_in[0];
        native_crc.out = ctl_data->crc_pipe[1];
	crc32_init(&native_crc.crc);
	ctl_data->child_in[0] = ctl_data->crc_pipe[0];
	native_crc.thread = g_thread_create(handle_crc_thread,
                                 (gpointer)&native_crc, TRUE, NULL);
    } else {
	native_crc.thread = NULL;
    }

    ctl_data->dar_pipe[0] = -1;
    ctl_data->dar_pipe[1] = -1;
    if (state_filename) {
	if (pipe(ctl_data->dar_pipe) == -1) {
	    error(_("extract_list - error setting up dar pipe: %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_in[1]);
	aclose(ctl_data->child_out[0]);
	aclose(ctl_data->child_err[0]);
	aclose(ctl_data->dar_pipe[0]);
	extract_files_child(ctl_data);
	/*NOTREACHED*/
    }

    stderr_isatty = isatty(fileno(stderr));

    if (ctl_data->pid == -1) {
	g_free(errstr);
	errstr = g_strdup(_("writer_intermediary - error forking child"));
	g_printf(_("writer_intermediary - error forking child"));
	return;
    }
    last_is_size = FALSE;
    aclose(ctl_data->child_in[0]);
    aclose(ctl_data->child_out[1]);
    aclose(ctl_data->child_err[1]);
    aclose(ctl_data->dar_pipe[1]);
    security_stream_read(amidxtaped_streams[DATAFD].fd, read_amidxtaped_data,
			 ctl_data);

    ctl_data->child_out_cdata.fd = ctl_data->child_out[0];
    ctl_data->child_out_cdata.output = stdout;
    ctl_data->child_out_cdata.name = NULL;
    ctl_data->child_out_cdata.buffer = NULL;
    ctl_data->child_out_cdata.event = event_create(
				(event_id_t)ctl_data->child_out[0],
				EV_READFD, handle_child_out,
				&ctl_data->child_out_cdata);
    event_activate(ctl_data->child_out_cdata.event);

    ctl_data->child_err_cdata.fd = ctl_data->child_err[0];
    ctl_data->child_err_cdata.output = stderr;
    ctl_data->child_err_cdata.name = NULL;
    ctl_data->child_err_cdata.buffer = NULL;
    ctl_data->child_err_cdata.event = event_create(
				(event_id_t)ctl_data->child_err[0],
				EV_READFD, handle_child_out,
				&ctl_data->child_err_cdata);
    event_activate(ctl_data->child_err_cdata.event);

    if (ctl_data->decrypt_cdata.fd != -1) {
	ctl_data->decrypt_cdata.event = event_create(
				(event_id_t)ctl_data->decrypt_cdata.fd,
				EV_READFD, handle_child_out,
				&ctl_data->decrypt_cdata);
	event_activate(ctl_data->decrypt_cdata.event);
    }

    if (ctl_data->decompress_cdata.fd != -1) {
	ctl_data->decompress_cdata.event = event_create(
				(event_id_t)ctl_data->decompress_cdata.fd,
				EV_READFD, handle_child_out,
				&ctl_data->decompress_cdata);
	event_activate(ctl_data->decompress_cdata.event);
    }

    if (am_has_feature(tapesrv_features, fe_amidxtaped_datapath)) {
	send_to_tape_server(amidxtaped_streams[CTLFD].fd, "DATAPATH-OK");
    }

    if (state_filename && ctl_data->bsu && ctl_data->bsu->dar) {
	ctl_data->dar_cdata.fd = ctl_data->dar_pipe[0];
	ctl_data->dar_cdata.name = g_strdup("DAR");
	ctl_data->dar_cdata.buffer = NULL;
	ctl_data->dar_cdata.event = event_create(
				(event_id_t)ctl_data->dar_pipe[0],
				EV_READFD, handle_dar_command,
				&ctl_data->dar_cdata);
	event_activate(ctl_data->dar_cdata.event);
    }
}

static gpointer
handle_crc_thread(
    gpointer data)
{
    send_crc_t *crc = (send_crc_t *)data;
    uint8_t  buf[32768];
    size_t   size;

    while ((size = full_read(crc->in, buf, 32768)) > 0) {
        if (full_write(crc->out, buf, size) == size) {
            crc32_add(buf, size, &crc->crc);
        }
    }
    close(crc->in);
    close(crc->out);

    return NULL;
}

