/*
 * 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 "conffile.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"

typedef struct EXTRACT_LIST_ITEM {
    char *path;

    struct EXTRACT_LIST_ITEM *next;
}
EXTRACT_LIST_ITEM;

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

    struct EXTRACT_LIST *next;
}
EXTRACT_LIST;

#define SKIP_TAPE 2
#define RETRY_TAPE 3

char *dump_device_name = NULL;

extern char *localhost;

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

static EXTRACT_LIST *extract_list = NULL;
static int tape_control_sock = -1;
static int tape_data_sock = -1;

#ifdef SAMBA_CLIENT
unsigned short samba_extract_method = SAMBA_TAR;
#endif /* SAMBA_CLIENT */

#define READ_TIMEOUT	240*60

EXTRACT_LIST *first_tape_list(void);
EXTRACT_LIST *next_tape_list(EXTRACT_LIST *list);
int is_extract_list_nonempty(void);
int length_of_tape_list(EXTRACT_LIST *tape_list);
void add_file(char *path, char *regex);
void add_glob(char *glob);
void add_regex(char *regex);
void clear_extract_list(void);
void clean_tape_list(EXTRACT_LIST *tape_list);
void clean_extract_list(void);
void delete_file(char *path, char *regex);
void delete_glob(char *glob);
void delete_regex(char *regex);
void delete_tape_list(EXTRACT_LIST *tape_list);
void display_extract_list(char *file);
void extract_files(void);
void read_file_header(char *buffer,
			dumpfile_t *file,
			size_t buflen,
			int tapedev);
void writer_intermediary(int ctl_fd, int data_fd, EXTRACT_LIST *elist);
void writer_intermediary(int ctl_fd, int data_fd, EXTRACT_LIST *elist);

static int add_extract_item(DIR_ITEM *ditem);
static int delete_extract_item(DIR_ITEM *ditem);
static int extract_files_setup(char *label, off_t fsf);
static int okay_to_continue(int allow_tape,
			int allow_skip,
			int allow_retry);
static int okay_to_continue(int, int,  int);
static ssize_t read_buffer(int datafd,
			char *buffer,
			size_t buflen,
			long timeout_s);
static void clear_tape_list(EXTRACT_LIST *tape_list);
static void extract_files_child(int in_fd, EXTRACT_LIST *elist);
static void send_to_tape_server(int tss, char *cmd);


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

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

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

    dataptr = buffer;
    spaceleft = (ssize_t)buflen;

    do {
        FD_ZERO(&readset);
        FD_SET(datafd, &readset);
        timeout.tv_sec = timeout_s;
        timeout.tv_usec = 0;
        nfound = select(datafd+1, &readset, NULL, NULL, &timeout);
        if(nfound < 0 ) {
            /* Select returned an error. */
	    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);
	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);
		pfn2->next = fn2;
	    } else if (remove_fn1 == 0) {
		pfn2 = fn2;
		fn2 = fn2->next;
	    }
	}

	if(remove_fn1 != 0) {
	    /* fn2->path is always valid */
	    /*@i@*/ dbprintf(_("removing path %s, it is included in %s\n"),
	    /*@i@*/	      fn1->path, fn2->path);
	    ofn1 = fn1;
	    fn1 = fn1->next;
	    amfree(ofn1->path);
	    if(pfn1 == NULL) {
		amfree(tape_list->files);
		tape_list->files = fn1;
	    } else {
		amfree(pfn1->next);
		pfn1->next = fn1;
	    }
	} else {
	    pfn1 = fn1;
	    fn1 = fn1->next;
	}
    }
}


void
clean_extract_list(void)
{
    EXTRACT_LIST *this;

    for (this = extract_list; this != NULL; this = this->next)
	clean_tape_list(this);
}


/* returns -1 if error */
/* returns  0 on succes */
/* returns  1 if already added */
static int
add_extract_item(
    DIR_ITEM *	ditem)
{
    EXTRACT_LIST *this, *this1;
    EXTRACT_LIST_ITEM *that, *curr;
    char *ditem_path = NULL;

    ditem_path = 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)) {
		    amfree(ditem_path);
		    return 1;
		}
		curr=curr->next;
	    }
	    that = (EXTRACT_LIST_ITEM *)g_malloc(sizeof(EXTRACT_LIST_ITEM));
            that->path = g_strdup(ditem_path);
	    that->next = this->files;
	    this->files = that;		/* add at front since easiest */
	    amfree(ditem_path);
	    return 0;
	}
    }

    /* so this is the first time we have seen this tape */
    this = (EXTRACT_LIST *)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 = g_strdup(ditem_path);
    that->next = NULL;
    this->files = that;

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


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

    ditem_path = 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);
		/* 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);
		    amfree(ditem_path);
		    return 0;
		}
		prev = that;
		that = that->next;
	    }
	    amfree(ditem_path);
	    return 1;
	}
    }

    amfree(ditem_path);
    return 1;
}

void
add_glob(
    char *	glob)
{
    char *regex;
    char *regex_path;
    char *s;
    char *uqglob = unquote_string(glob);

    regex = glob_to_regex(uqglob);
    dbprintf(_("add_glob (%s) -> %s\n"), uqglob, regex);
    if ((s = validate_regexp(regex)) != NULL) {
	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);
    }
    amfree(regex);
    amfree(uqglob);
}

void
add_regex(
    char *	regex)
{
    char *s;
    char *uqregex = unquote_string(regex);

    if ((s = validate_regexp(uqregex)) != NULL) {
	g_printf(_("%s is not a valid regular expression: "), regex);
	puts(s);
    } else {
        add_file(uqregex, regex);
    }
    amfree(uqregex);
}

void add_file(
    char *	path,
    char *	regex)
{
    DIR_ITEM *ditem, lditem;
    char *path_on_disk = NULL;
    char *path_on_disk_slash = NULL;
    char *cmd = NULL;
    char *err = NULL;
    int i;
    ssize_t j;
    char *dir_undo, dir_undo_ch = '\0';
    char *ditem_path = NULL;
    char *l = NULL;
    int  added;
    char *s, *fp, *quoted;
    int ch;
    int found_one;
    int dir_entries;

    if (disk_path == NULL) {
	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 '/' */
	    path_on_disk = g_strdup(regex);
	} else {
	    /* Prepend '/' */
	    path_on_disk = g_strconcat("/", regex, NULL);
	}
    } else {
	char *clean_disk_path = clean_regex(disk_path, 0);
	path_on_disk = g_strjoin(NULL, clean_disk_path, "/", regex, NULL);
	amfree(clean_disk_path);
    }

    path_on_disk_slash = g_strconcat(path_on_disk, "/", NULL);

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

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

		g_free(cmd);
		cmd = g_strconcat("ORLD ", ditem_path, NULL);
		if(send_command(cmd) == -1) {
		    amfree(cmd);
		    amfree(ditem_path);
		    amfree(path_on_disk);
		    amfree(path_on_disk_slash);
		    exit(1);
		}
		amfree(cmd);
		cmd = NULL;
		/* skip preamble */
		if ((i = get_reply_line()) == -1) {
		    amfree(ditem_path);
		    amfree(path_on_disk);
		    amfree(path_on_disk_slash);
		    exit(1);
		}
		if(i==0) {		/* assume something wrong */
		    amfree(ditem_path);
		    amfree(path_on_disk);
		    amfree(path_on_disk_slash);
		    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);
                lditem.path = g_strdup(ditem->path);
		/* skip the last line -- duplicate of the preamble */

		while ((i = get_reply_line()) != 0) {
		    if (i == -1) {
			amfree(ditem_path);
		        amfree(path_on_disk);
		        amfree(path_on_disk_slash);
			exit(1);
		    }
		    if(err) {
			if(cmd == NULL) {
			    if(dir_undo) *dir_undo = dir_undo_ch;
			    dir_undo = NULL;
			    cmd = 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_non_whitespace(s, ch);
                    s[-1] = '\0';
                    g_free(lditem.tape);
                    lditem.tape = g_strdup(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.path);
			g_printf(_("Added dir %s at date %s\n"),
			       quoted, lditem.date);
			dbprintf(_("add_file: (Successful) Added dir %s at date %s\n"),
				  quoted, lditem.date);
			amfree(quoted);
			added=1;
			break;

		    case  1:
			break;
		    }
		}
		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->path);
		    g_printf(_("Added file %s\n"), quoted);
		    dbprintf(_("add_file: (Successful) Added %s\n"), quoted);
		    amfree(quoted);
		    break;

		case  1:
		    quoted = quote_string(ditem->path);
		    g_printf(_("File %s already added\n"), quoted);
		    dbprintf(_("add_file: file %s already added\n"), quoted);
		    amfree(quoted);
		    break;
		}
	    }
	}
    }
    if (cmd != NULL)
	amfree(cmd);
    amfree(ditem_path);
    amfree(path_on_disk);
    amfree(path_on_disk_slash);
    amfree(lditem.path);
    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 = unquote_string(glob);

    regex = glob_to_regex(uqglob);
    dbprintf(_("delete_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, "[/]*$");
        delete_file(uqglob, regex_path);
        amfree(regex_path);
    }
    amfree(regex);
    amfree(uqglob);
}

void
delete_regex(
    char *	regex)
{
    char *s;
    char *uqregex = unquote_string(regex);

    if ((s = validate_regexp(regex)) != NULL) {
	g_printf(_("\"%s\" is not a valid regular expression: "), regex);
	puts(s);
    } else {
	delete_file(uqregex, uqregex);
    }
    amfree(uqregex);
}

void
delete_file(
    char *	path,
    char *	regex)
{
    DIR_ITEM *ditem, lditem;
    char *path_on_disk = NULL;
    char *path_on_disk_slash = NULL;
    char *cmd = NULL;
    char *err = NULL;
    int i;
    ssize_t j;
    char *date;
    char *tape, *tape_undo, tape_undo_ch = '\0';
    char *dir_undo, dir_undo_ch = '\0';
    int  level = 0;
    char *ditem_path = NULL;
    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"), path);

    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: "/." */
		path_on_disk = g_strdup("/\\.[/]*$");
	    } else {
		/* No mods needed if already starts with '/' */
		path_on_disk = g_strdup(regex);
	    }
	} else {
	    /* Prepend '/' */
	    path_on_disk = g_strconcat("/", regex, NULL);
	}
    } else {
	char *clean_disk_path = clean_regex(disk_path, 0);
	path_on_disk = g_strjoin(NULL, clean_disk_path, "/", regex, NULL);
	amfree(clean_disk_path);
    }

    path_on_disk_slash = g_strconcat(path_on_disk, "/", NULL);

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

		g_free(cmd);
		cmd = g_strconcat("ORLD ", ditem_path, NULL);
		if(send_command(cmd) == -1) {
		    amfree(cmd);
		    amfree(ditem_path);
		    amfree(path_on_disk);
		    amfree(path_on_disk_slash);
		    exit(1);
		}
		amfree(cmd);
		/* skip preamble */
		if ((i = get_reply_line()) == -1) {
		    amfree(ditem_path);
		    amfree(path_on_disk);
		    amfree(path_on_disk_slash);
		    exit(1);
		}
		if(i==0)		/* assume something wrong */
		{
		    amfree(ditem_path);
		    amfree(path_on_disk);
		    amfree(path_on_disk_slash);
		    amfree(lditem.path);
		    amfree(lditem.date);
		    amfree(lditem.tape);
		    l = reply_line();
		    g_printf("%s\n", l);
		    return;
		}
		deleted=0;
                g_free(lditem.path);
                lditem.path = g_strdup(ditem->path);
		amfree(cmd);
		tape_undo = dir_undo = NULL;
		/* skip the last line -- duplicate of the preamble */
		while ((i = get_reply_line()) != 0)
		{
		    if (i == -1) {
			amfree(ditem_path);
			amfree(path_on_disk);
			amfree(path_on_disk_slash);
			exit(1);
		    }
		    if(err) {
			if(cmd == NULL) {
			    if(tape_undo) *tape_undo = tape_undo_ch;
			    if(dir_undo) *dir_undo = dir_undo_ch;
			    tape_undo = dir_undo = NULL;
			    cmd = 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 = g_strdup(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_path, date);
			dbprintf(_("delete_file: (Successful) Deleted dir %s at date %s\n"),
				  ditem_path, date);
			deleted=1;
			break;
		    case  1:
			break;
		    }
		}
		if(!server_happy()) {
		    puts(reply_line());
		} else if(err) {
		    if (*err)
			puts(err);
		    if (cmd)
			puts(cmd);
		} else if(deleted == 0) {
		    g_printf(_("Warning - dir '%s' not on tape list\n"),
			   ditem_path);
		    dbprintf(_("delete_file: dir '%s' not on tape list\n"),
			      ditem_path);
		}
	    }
	    else
	    {
		switch(delete_extract_item(ditem)) {
		case -1:
		    g_printf(_("System error\n"));
		    dbprintf(_("delete_file: (Failed) System error\n"));
		    break;
		case  0:
		    g_printf(_("Deleted %s\n"), ditem->path);
		    dbprintf(_("delete_file: (Successful) Deleted %s\n"),
			      ditem->path);
		    break;
		case  1:
		    g_printf(_("Warning - file '%s' not on tape list\n"),
			   ditem->path);
		    dbprintf(_("delete_file: file '%s' not on tape list\n"),
			      ditem->path);
		    break;
		}
	    }
	}
    }
    amfree(cmd);
    amfree(ditem_path);
    amfree(path_on_disk);
    amfree(path_on_disk_slash);
    amfree(lditem.path);
    amfree(lditem.date);
    amfree(lditem.tape);

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


/* print extract list into file. If NULL ptr passed print to screen */
void
display_extract_list(
    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->path);
    }

    if (file == NULL) {
	apclose(fp);
    } else {
	g_printf(_("Extract list written to file %s\n"), file);
	afclose(fp);
    }
}


/* returns 0 if extract list empty and 1 if it isn't */
int
is_extract_list_nonempty(void)
{
    return (extract_list != NULL);
}


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

    get_tape = 0;
    while (ret < 0) {
	if (get_tape) {
	    prompt = _("New tape device [?]: ");
	} else if (allow_tape && allow_skip) {
	    prompt = _("Continue [?/Y/n/s/t]? ");
	} else if (allow_tape && !allow_skip) {
	    prompt = _("Continue [?/Y/n/t]? ");
	} else if (allow_retry) {
	    prompt = _("Continue [?/Y/n/r]? ");
	} else {
	    prompt = _("Continue [?/Y/n]? ");
	}
	fputs(prompt, stdout);
	fflush(stdout); fflush(stderr);
	amfree(line);
	if ((line = agets(stdin)) == NULL) {
	    putchar('\n');
	    clearerr(stdin);
	    if (get_tape) {
		get_tape = 0;
		continue;
	    }
	    ret = 0;
	    break;
	}
	s = line;
	while ((ch = *s++) != '\0' && g_ascii_isspace(ch)) {
	    (void)ch;  /* Quiet empty loop body warning */
	}
	if (ch == '?') {
	    if (get_tape) {
		g_printf(_("Enter a new device ([host:]device) 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 \"t\"ape to change tape drives"));
		}
		putchar('\n');
	    }
	} else if (get_tape) {
	    set_tape(s - 1);
	    get_tape = 0;
	} else if (ch == '\0' || ch == 'Y' || ch == 'y') {
	    ret = 1;
	} else if (allow_tape && (ch == 'T' || ch == 't')) {
	    get_tape = 1;
	} else if (ch == 'N' || ch == 'n') {
	    ret = 0;
	} else if (allow_retry && (ch == 'R' || ch == 'r')) {
	    ret = RETRY_TAPE;
	} else if (allow_skip && (ch == 'S' || ch == 's')) {
	    ret = SKIP_TAPE;
	}
    }
    /*@ignore@*/
    amfree(line);
    /*@end@*/
    return ret;
}

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

    if (full_write(tss, msg, strlen(msg)) < strlen(msg))
    {
	error(_("Error writing to tape server: %s"), strerror(errno));
	/*NOTREACHED*/
    }
    amfree(msg);
}


/* start up connection to tape server and set commands to initiate
   transfer of dump image.
   Return tape server socket on success, -1 on error. */
static int
extract_files_setup(
    char *	label,
    off_t	fsf)
{
    struct servent *sp;
    in_port_t my_port, my_data_port;
    char *disk_regex = NULL;
    char *host_regex = NULL;
    char *service_name = NULL;
    char *line = NULL;
    char *clean_datestamp, *ch, *ch1;
    char *our_feature_string = NULL;
    char *tt = NULL;
    char *stream_msg = NULL;

    service_name = "amidxtape";

    /* get tape server details */
    if ((sp = getservbyname(service_name, "tcp")) == NULL)
    {
	g_printf(_("%s/tcp unknown protocol - config error?\n"), service_name);
	return -1;
    }
    if (seteuid(0) != 0) { error("Can't set euid"); }; /* it either works ... */
    if (setegid(0) != 0) { error("Can't set egid"); };
    tape_control_sock = stream_client_privileged(NULL,
						  tape_server_name,
						  (in_port_t)ntohs((in_port_t)sp->s_port),
						  0,
						  STREAM_BUFSIZE,
						  &my_port,
						  0, &stream_msg);
    if (stream_msg) {
	g_printf(_("cannot connect to %s: %s\n"), tape_server_name, stream_msg);
	g_free(stream_msg);
	return -1;
    }
    if (tape_control_sock < 0) {
	g_printf(_("cannot connect to %s: %s\n"), tape_server_name, strerror(errno));
	return -1;
    }
    if (my_port >= IPPORT_RESERVED) {
	aclose(tape_control_sock);
	g_printf(_("did not get a reserved port: %u\n"), (unsigned)my_port);
	return -1;
    }
 
    if (setegid(getgid()) != 0) { error("Can't set gid"); };
    if (seteuid(getuid()) != 0) { error("Can't set uid"); }; /* put it back */

    /* do the security thing */
    line = get_security();
    send_to_tape_server(tape_control_sock, line);
    memset(line, '\0', strlen(line));
    amfree(line);

    disk_regex = g_malloc(strlen(disk_name) * 2 + 3);

    ch = disk_name;
    ch1 = disk_regex;

    /* we want to force amrestore to only match disk_name exactly */
    *(ch1++) = '^';

    /* We need to escape some characters first... NT compatibilty crap */
    for (; *ch != 0; ch++, ch1++) {
	switch (*ch) {     /* done this way in case there are more */
	case '$':
	    *(ch1++) = '\\';
	    /* no break; we do want to fall through... */
	default:
	    *ch1 = *ch;
	}
    }

    /* we want to force amrestore to only match disk_name exactly */
    *(ch1++) = '$';

    *ch1 = '\0';

    host_regex = g_malloc(strlen(dump_hostname) * 2 + 3);

    ch = dump_hostname;
    ch1 = host_regex;

    /* we want to force amrestore to only match dump_hostname exactly */
    *(ch1++) = '^';

    /* We need to escape some characters first... NT compatibilty crap */
    for (; *ch != 0; ch++, ch1++) {
	switch (*ch) {     /* done this way in case there are more */
	case '$':
	    *(ch1++) = '\\';
	    /* no break; we do want to fall through... */
	default:
	    *ch1 = *ch;
	}
    }

    /* we want to force amrestore to only match dump_hostname exactly */
    *(ch1++) = '$';

    *ch1 = '\0';

    clean_datestamp = 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)){
	char buffer[32768] = "\0";

	our_feature_string = am_feature_to_string(our_features);
	g_free(tt);
	tt = g_strconcat("FEATURES=", our_feature_string, NULL);
	send_to_tape_server(tape_control_sock, tt);
	if (read(tape_control_sock, buffer, sizeof(buffer)) <= 0) {
	    error(_("Could not read features from control socket\n"));
	    /*NOTREACHED*/
	}
	tapesrv_features = am_string_to_feature(buffer);
	amfree(our_feature_string);
    }


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

	if(am_has_feature(indexsrv_features, fe_amidxtaped_config)) {
	    g_free(tt);
	    tt = g_strconcat("CONFIG=", config, NULL);
	    send_to_tape_server(tape_control_sock, tt);
	}
	if(am_has_feature(indexsrv_features, fe_amidxtaped_label) &&
	   label && label[0] != '/') {
	    g_free(tt);
	    tt = g_strconcat("LABEL=", label, NULL);
	    send_to_tape_server(tape_control_sock, 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(tape_control_sock, tt);
	}
	send_to_tape_server(tape_control_sock, "HEADER");
	g_free(tt);
	tt = g_strconcat("DEVICE=", dump_device_name, NULL);
	send_to_tape_server(tape_control_sock, tt);
	g_free(tt);
	tt = g_strconcat("HOST=", host_regex, NULL);
	send_to_tape_server(tape_control_sock, tt);
	g_free(tt);
	tt = g_strconcat("DISK=", disk_regex, NULL);
	send_to_tape_server(tape_control_sock, tt);
	g_free(tt);
	tt = g_strconcat("DATESTAMP=", clean_datestamp, NULL);
	send_to_tape_server(tape_control_sock, tt);
	send_to_tape_server(tape_control_sock, "END");
	amfree(tt);
    }
    else if(am_has_feature(indexsrv_features, fe_amidxtaped_nargs)) {
	/* send to the tape server what tape file we want */
	/* 6 args:
	 *   "-h"
	 *   "-p"
	 *   "tape device"
	 *   "hostname"
	 *   "diskname"
	 *   "datestamp"
	 */
	send_to_tape_server(tape_control_sock, "6");
	send_to_tape_server(tape_control_sock, "-h");
	send_to_tape_server(tape_control_sock, "-p");
	send_to_tape_server(tape_control_sock, dump_device_name);
	send_to_tape_server(tape_control_sock, host_regex);
	send_to_tape_server(tape_control_sock, disk_regex);
	send_to_tape_server(tape_control_sock, clean_datestamp);

	dbprintf(_("Started amidxtaped with arguments \"6 -h -p %s %s %s %s\"\n"),
		  dump_device_name, host_regex, disk_regex, clean_datestamp);
    }

    /*
     * split-restoring amidxtaped versions will expect to set up a data
     * connection for dumpfile data, distinct from the socket we're already
     * using for control data
     */

    if(am_has_feature(tapesrv_features, fe_recover_splits)){
	char buffer[32768+1];
	in_port_t data_port = (in_port_t)-1;
        ssize_t nread;
	char *stream_msg = NULL;

        nread = read(tape_control_sock, buffer, sizeof(buffer)-1);

	if (nread <= 0) {
	    error(_("Could not read from control socket: %s\n"),
                  strerror(errno));
	    /*NOTREACHED*/
        }

	buffer[nread] = '\0';
        if (sscanf(buffer, "CONNECT %hu\n",
		(unsigned short *)&data_port) != 1) {
	    error(_("Received invalid port number message from control socket: %s\n"),
                  buffer);
	    /*NOTREACHED*/
        }

	tape_data_sock = stream_client_privileged(NULL,
						  server_name,
						  data_port,
						  0,
						  STREAM_BUFSIZE,
						  &my_data_port,
						  0, &stream_msg);
	if (stream_msg) {
	    error("Unable to make data connection to server: %s\n", stream_msg);
	    /*NOTREACHED*/
	}
	if (tape_data_sock == -1) {
	    error("Unable to make data connection to server: %s\n",
		      strerror(errno));
	    /*NOTREACHED*/
	}

	amfree(our_feature_string);

	line = get_security();

	send_to_tape_server(tape_data_sock, line);
	memset(line, '\0', strlen(line));
	amfree(line);
    }

    amfree(disk_regex);
    amfree(host_regex);
    amfree(clean_datestamp);

    return tape_control_sock;
}


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

void
read_file_header(
    char *	buffer,
    dumpfile_t *file,
    size_t	buflen,
    int		tapedev)
{
    ssize_t bytes_read;

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

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

static void
extract_files_child(
    int			in_fd,
    EXTRACT_LIST *	elist)
{
    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;
    char buffer[DISK_BLOCK_BYTES];
    dumpfile_t file;
    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(in_fd, STDIN_FILENO) == -1)
    {
	error(_("dup2 failed in extract_files_child: %s"), strerror(errno));
	/*NOTREACHED*/
    }

    /* read the file header */
    fh_init(&file);
    read_file_header(buffer, &file, sizeof(buffer), STDIN_FILENO);

    if(file.type != F_DUMPFILE) {
	print_header(stdout, &file);
	error(_("bad header"));
	/*NOTREACHED*/
    }

    if (file.program[0] != '\0') {
#ifdef GNUTAR
	if (g_str_equal(file.program, GNUTAR))
	    dumptype = IS_GNUTAR;
#endif

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

#ifdef SAMBA_CLIENT
	if (dumptype == IS_UNKNOWN && g_str_equal(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(elist);
    switch(dumptype) {
#ifdef SAMBA_CLIENT
    case IS_SAMBA:
	g_ptr_array_add(argv_ptr, g_strdup("smbclient"));
	smbpass = findpass(file.disk, &domain);
	if (smbpass) {
	    g_ptr_array_add(argv_ptr, g_strdup(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"));
	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(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(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
    }

    for (i = 0, fn = elist->files; i < files_off_tape; i++, fn = fn->next)
    {
	switch (dumptype) {
	case IS_TAR:
	case IS_GNUTAR:
	case IS_SAMBA_TAR:
#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(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));
	    }
	}
    }
#if defined(XFSDUMP)
    if (g_str_equal(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(file.program, DUMP)) {
	    cmd = g_strdup(RESTORE);
	}
#endif
#if defined(VDUMP)
	if (g_str_equal(file.program, VDUMP)) {
	    cmd = g_strdup(VRESTORE);
	}
#endif
#if defined(VXDUMP)
	if (g_str_equal(file.program, VXDUMP)) {
	    cmd = g_strdup(VXRESTORE);
	}
#endif
#if defined(XFSDUMP)
	if (g_str_equal(file.program, XFSDUMP)) {
	    cmd = g_strdup(XFSRESTORE);
	}
#endif
	if (cmd == NULL) {
	    g_fprintf(stderr, _("warning: restore program for %s not available.\n"),
		    file.program);
	    cmd = g_strdup("restore");
	}
    }
    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));
	}
	safe_fd(-1, 0);
        (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.
 */
void
writer_intermediary(
    int			ctl_fd,
    int			data_fd,
    EXTRACT_LIST *	elist)
{
    int child_pipe[2];
    pid_t pid;
    char buffer[DISK_BLOCK_BYTES];
    size_t bytes_read;
    amwait_t extractor_status;
    int max_fd, nfound;
    SELECT_ARG_TYPE readset, selectset;
    struct timeval timeout;

    /*
     * If there's no distinct data channel (such as if we're talking to an
     * older server), don't bother doing anything complicated.  Just run the
     * extraction.
     */
    if(data_fd == -1){
	extract_files_child(ctl_fd, elist);
	/*NOTREACHED*/
    }

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

    /* okay, ready to extract. fork a child to do the actual work */
    if ((pid = fork()) == 0) {
	/* this is the child process */
	/* never gets out of this clause */
	aclose(child_pipe[1]);
        extract_files_child(child_pipe[0], elist);
	/*NOTREACHED*/
    }

    /* This is the parent */
    if (pid == -1) {
	error(_("writer_intermediary - error forking child"));
	/*NOTREACHED*/
    }

    aclose(child_pipe[0]);

    if(data_fd > ctl_fd) max_fd = data_fd+1;
                    else max_fd = ctl_fd+1;
    FD_ZERO(&readset);
    FD_SET(data_fd, &readset);
    FD_SET(ctl_fd, &readset);

    do {
	timeout.tv_sec = READ_TIMEOUT;
	timeout.tv_usec = 0;
	FD_COPY(&readset, &selectset);

	nfound = select(max_fd, &selectset, NULL, NULL,
			&timeout);
	if(nfound < 0) {
	    g_fprintf(stderr,_("select error: %s\n"), strerror(errno));
	    break;
	}

	if (nfound == 0) { /* timeout */
	    g_fprintf(stderr, _("timeout waiting %d seconds for restore\n"),
		    READ_TIMEOUT);
	    g_fprintf(stderr, _("increase READ_TIMEOUT in recover-src/extract_list.c if your tape is slow\n"));
	    break;
	}

	if(FD_ISSET(ctl_fd, &selectset)) {
	    bytes_read = read(ctl_fd, buffer, sizeof(buffer)-1);
	    switch(bytes_read) {
            case -1:
                if ((errno != EINTR) && (errno != EAGAIN)) {
                    if (errno != EPIPE) {
                        g_fprintf(stderr,_("writer ctl fd read error: %s"),
                                strerror(errno));
                    }
                    FD_CLR(ctl_fd, &readset);
                }
                break;

            case  0:
                FD_CLR(ctl_fd, &readset);
                break;

            default: {
                char desired_tape[MAX_TAPE_LABEL_BUF];

                buffer[bytes_read] = '\0';
                /* if prompted for a tape, relay said prompt to the user */
                if(sscanf(buffer, "FEEDME %132s\n", desired_tape) == 1) {
                    int done = 0;
                    while (!done) {
                        char *input = NULL;
                        g_printf(_("Please insert tape %s. Continue? [Y|n]: "),
                               desired_tape);
                        fflush(stdout);

                        input = agets(stdin); /* strips \n */
                        if (input &&
			    (strcasecmp("", input) == 0 ||
                             strcasecmp("y", input) == 0 ||
                             strcasecmp("yes", input) == 0)) {
                            send_to_tape_server(tape_control_sock, "OK");
                            done = 1;
                        } else if (!input ||
				   strcasecmp("n", input) == 0 ||
                                   strcasecmp("no", input) == 0) {
                            send_to_tape_server(tape_control_sock, "ERROR");
                            /* Abort!
                               We are the middle process, so just die. */
                            exit(EXIT_FAILURE);
                        }
                        amfree(input);
                    }
                } else {
                    g_fprintf(stderr, _("Strange message from tape server: %s"), buffer);
		    break;
                }
	      }
            }
        }

        /* now read some dump data */
        if(FD_ISSET(data_fd, &selectset)) {
            bytes_read = read(data_fd, buffer, sizeof(buffer)-1);
            switch(bytes_read) {
            case -1:
                if ((errno != EINTR) && (errno != EAGAIN)) {
                    if (errno != EPIPE) {
                        g_fprintf(stderr,_("writer data fd read error: %s"),
                                strerror(errno));
                    }
                    FD_CLR(data_fd, &readset);
                }
                break;

            case  0:
                FD_CLR(data_fd, &readset);
                break;

            default:
                /*
                 * spit what we got from the server to the child
                 *  process handling actual dumpfile extraction
                 */
                if(full_write(child_pipe[1], buffer, bytes_read) < bytes_read) {
                    if(errno == EPIPE) {
                        error(_("pipe data reader has quit: %s\n"),
                              strerror(errno));
                        /* NOTREACHED */
                    }
                    error(_("Write error to extract child: %s\n"),
                          strerror(errno));
                    /* NOTREACHED */
                }
                break;
	    }
	}
    } while(FD_ISSET(ctl_fd, &readset) || FD_ISSET(data_fd, &readset));

    aclose(child_pipe[1]);

    waitpid(pid, &extractor_status, 0);
    if(WEXITSTATUS(extractor_status) != 0){
	int ret = WEXITSTATUS(extractor_status);
        if(ret == 255) ret = -1;
	error(_("Extractor child exited with status %d\n"), ret);
	/*NOTREACHED*/
    }

    exit(0);
}

/* exec restore to do the actual restoration */

/* does the actual extraction of files */
/*
 * The original design had the dump image being returned exactly as it
 * appears on the tape, and this routine getting from the index server
 * whether or not it is compressed, on the assumption that the tape
 * server may not know how to uncompress it. But
 * - Amrestore can't do that. It returns either compressed or uncompressed
 * (always). Amrestore assumes it can uncompress files. It is thus a good
 * idea to run the tape server on a machine with gzip.
 * - The information about compression in the disklist is really only
 * for future dumps. It is possible to change compression on a drive
 * so the information in the disklist may not necessarily relate to
 * the dump image on the tape.
 *   Consequently the design was changed to assuming that amrestore can
 * uncompress any dump image and have it return an uncompressed file
 * always.
 */
void
extract_files(void)
{
    EXTRACT_LIST *elist;
    pid_t pid;
    amwait_t child_stat;
    char buf[STR_SIZE];
    char *l;
    int first;
    int otc;
    tapelist_t *tlist = NULL;

    if (!is_extract_list_nonempty())
    {
	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]!='/') {
	    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( ; tlist != NULL; tlist = tlist->next)
		g_printf(" %s", tlist->label);
	    g_printf("\n");
	    amfree(tlist);
	}
    first=1;
    for (elist = first_tape_list(); elist != NULL; elist = next_tape_list(elist))
    {
	if(elist->tape[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( ; tlist != NULL; tlist = tlist->next)
		g_printf(" %s", tlist->label);
	    g_printf("\n");
	    amfree(tlist);
	}
    }
    g_printf("\n");

    if (getcwd(buf, sizeof(buf)) == NULL) {
	perror(_("extract_list: Cannot determine current working directory"));
	exit(1);
    }

    g_printf(_("Restoring files into directory %s\n"), buf);
#ifdef SAMBA_CLIENT
    if (samba_extract_method == SAMBA_SMBCLIENT)
      g_printf(_("(unless it is a Samba backup, that will go through to the SMB server)\n"));
#endif
    if (!okay_to_continue(0,0,0))
	return;
    g_printf("\n");

    while ((elist = first_tape_list()) != NULL)
    {
	if(elist->tape[0]=='/') {
	    g_free(dump_device_name);
	    dump_device_name = g_strdup(elist->tape);
	    g_printf(_("Extracting from file "));
	    tlist = unmarshal_tapelist_str(dump_device_name,
			am_has_feature(indexsrv_features,
				       fe_amrecover_storage_in_marshall));
	    for( ; tlist != NULL; tlist = tlist->next)
		g_printf(" %s", tlist->label);
	    g_printf("\n");
	    amfree(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);
	    amfree(tlist->label);
	    amfree(tlist);
	    otc = okay_to_continue(1,1,0);
	    if (otc == 0)
	        return;
	    else if (otc == SKIP_TAPE) {
		delete_tape_list(elist); /* skip this tape */
		continue;
	    }
	    g_free(dump_device_name);
	    dump_device_name = g_strdup(tape_device_name);
	}
	g_free(dump_datestamp);
	dump_datestamp = g_strdup(elist->date);

	/* connect to the tape handler daemon on the tape drive server */
	if ((tape_control_sock = extract_files_setup(elist->tape, elist->fileno)) == -1)
	{
	    g_fprintf(stderr, _("amrecover - can't talk to tape server\n"));
	    return;
	}

	/* okay, ready to extract. fork a child to do the actual work */
	if ((pid = fork()) == 0)
	{
	    /* this is the child process */
	    /* never gets out of this clause */
	    writer_intermediary(tape_control_sock, tape_data_sock, elist);
	    /*NOT REACHED*/
	}
	/* this is the parent */
	if (pid == -1)
	{
	    perror(_("extract_list - error forking child"));
	    aclose(tape_control_sock);
	    exit(1);
	}

	/* store the child pid globally so that it can be killed on intr */
	extract_restore_child_pid = pid;

	/* wait for the child process to finish */
	if ((pid = waitpid(-1, &child_stat, 0)) == (pid_t)-1)
	{
	    perror(_("extract_list - error waiting for child"));
	    exit(1);
	}

	if(tape_data_sock != -1) {
	    aclose(tape_data_sock);
	}

	if (pid == extract_restore_child_pid)
	{
	    extract_restore_child_pid = -1;
	}
	else
	{
	    g_fprintf(stderr, _("extract list - unknown child terminated?\n"));
	    exit(1);
	}
	if ((WIFEXITED(child_stat) != 0) && (WEXITSTATUS(child_stat) != 0))
	{
	    g_fprintf(stderr,
		    _("extract_list - child returned non-zero status: %d\n"),
		    WEXITSTATUS(child_stat));
	    otc = okay_to_continue(0,0,1);
	    if(otc == 0)
		return;

	    if(otc == 1) {
		delete_tape_list(elist); /* tape failed so delete from list */
	    }
	}
	else {
	    delete_tape_list(elist);	/* tape done so delete from list */
	}
    }
}
