/*
 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
 * Copyright (c) 1991-1999 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: sendbackup.c,v 1.88 2006/07/25 18:27:56 martinea Exp $
 *
 * common code for the sendbackup-* programs.
 */

#include "amanda.h"
#include "match.h"
#include "sendbackup.h"
#include "clock.h"
#include "pipespawn.h"
#include "amfeatures.h"
#include "getfsent.h"
#include "conffile.h"
#include "amandates.h"
#include "stream.h"
#include "shm-ring.h"

#define sendbackup_debug(i, ...) do {	\
	if ((i) <= debug_sendbackup) {	\
	    dbprintf(__VA_LIST__);	\
	}				\
} while (0)

#define TIMEOUT 30

pid_t comppid = (pid_t)-1;
pid_t dumppid = (pid_t)-1;
pid_t tarpid = (pid_t)-1;
pid_t encpid = (pid_t)-1;
pid_t indexpid = (pid_t)-1;
pid_t application_api_pid = (pid_t)-1;
char *errorstr = NULL;

int datafd;
int mesgfd;
int indexfd;
int statefd;
FILE   *mesgstream = NULL;

g_option_t *g_options = NULL;

long dump_size = -1;

backup_program_t *program = NULL;
dle_t *gdle = NULL;

char *shm_control_name = NULL;
send_crc_t native_crc;
send_crc_t client_crc;
gboolean   have_filter = FALSE;
shm_ring_t *shm_ring = NULL;

static am_feature_t *our_features = NULL;
static char *our_feature_string = NULL;
static char *amandad_auth = NULL;

typedef struct filter_stderr_pipe {
    int fd;
    GThread *thread;
} filter_stderr_pipe;
static filter_stderr_pipe enc_stderr_pipe;
static filter_stderr_pipe comp_stderr_pipe;

/* local functions */
int main(int argc, char **argv);
char *childstr(pid_t pid);
int check_status(pid_t pid, amwait_t w, int mesgfd);

pid_t pipefork(void (*func)(void), char *fname, int *stdinfd,
		int stdoutfd, int stderrfd);
int check_result(int mesgfd);
void parse_backup_messages(dle_t *dle, int mesgin);
static void process_dumpline(char *str);
static void save_fd(int *, int);
void application_api_info_tapeheader(int mesgfd, char *prog, dle_t *dle);

gpointer stderr_thread(gpointer data);

int
fdprintf(
    int   fd,
    char *format,
    ...)
{
    va_list  argp;
    char    *s;
    int      r;

    arglist_start(argp, format);
    s = g_strdup_vprintf(format, argp);
    arglist_end(argp);

    r = full_write(fd, s, strlen(s));
    amfree(s);
    return r;
}

int
main(
    int		argc,
    char **	argv)
{
    int interactive = 0;
    int level = 0;
    int mesgpipe[2];
    dle_t *dle = NULL;
    char *dumpdate, *stroptions;
    char *qdisk = NULL;
    char *qamdevice = NULL;
    char *line = NULL;
    char *err_extra = NULL;
    char *s;
    int i;
    int ch;
    GSList *errlist;
    am_level_t *alevel;
    int scripts_exit_status;
    char  **env;

    if (argc > 1 && argv[1] && g_str_equal(argv[1], "--version")) {
	printf("sendbackup-%s\n", VERSION);
	return (0);
    }

    glib_init();

    /* initialize */
    /*
     * Configure program for internationalization:
     *   1) Only set the message locale for now.
     *   2) Set textdomain for all amanda related programs to "amanda"
     *      We don't want to be forced to support dozens of message catalogs.
     */  
    setlocale(LC_MESSAGES, "C");
    textdomain("amanda"); 

    safe_fd(DATA_FD_OFFSET, DATA_FD_COUNT*2);
    openbsd_fd_inform();

    safe_cd();

    set_pname("sendbackup");

    /* Don't die when child closes pipe */
    signal(SIGPIPE, SIG_IGN);

    /* Don't die when interrupt received */
    signal(SIGINT, SIG_IGN);

    if(argc > 1 && g_str_equal(argv[1], "-t")) {
	interactive = 1;
	argc--;
	argv++;
    } else {
	interactive = 0;
    }

    make_crc_table();

    add_amanda_log_handler(amanda_log_stderr);
    add_amanda_log_handler(amanda_log_syslog);
    dbopen(DBG_SUBDIR_CLIENT);
    startclock();
    dbprintf(_("Version %s\n"), VERSION);

    if(argc > 2 && g_str_equal(argv[1], "amandad")) {
	amandad_auth = g_strdup(argv[2]);
	argc -= 2;
	argv += 2;
    }

    if (argc > 2 && g_str_equal(argv[1], "--shm-name")) {
	shm_control_name = g_strdup(argv[2]);
	argc -= 2;
	argv += 2;
    }

    our_features = am_init_feature_set();
    our_feature_string = am_feature_to_string(our_features);

    config_init(CONFIG_INIT_CLIENT|CONFIG_INIT_GLOBAL, NULL);
    /* (check for config errors comes later) */

    check_running_as(RUNNING_AS_CLIENT_LOGIN);

    if(interactive) {
	/*
	 * In interactive (debug) mode, the backup data is sent to
	 * /dev/null and none of the network connections back to driver
	 * programs on the tape host are set up.  The index service is
	 * run and goes to stdout.
	 */
	g_fprintf(stderr, _("%s: running in interactive test mode\n"), get_pname());
	fflush(stderr);
    }

    qdisk = NULL;
    dumpdate = NULL;
    stroptions = NULL;

    for(; (line = agets(stdin)) != NULL; free(line)) {
	if (line[0] == '\0')
	    continue;
	if(interactive) {
	    g_fprintf(stderr, "%s> ", get_pname());
	    fflush(stderr);
	}
	if(strncmp_const(line, "OPTIONS ") == 0) {
	    g_options = parse_g_options(line+8, 1);
	    if(!g_options->hostname) {
		g_options->hostname = g_malloc(MAX_HOSTNAME_LENGTH+1);
		gethostname(g_options->hostname, MAX_HOSTNAME_LENGTH);
		g_options->hostname[MAX_HOSTNAME_LENGTH] = '\0';
	    }

	    if (g_options->config) {
		/* overlay this configuration on the existing (nameless) configuration */
		config_init(CONFIG_INIT_CLIENT | CONFIG_INIT_EXPLICIT_NAME | CONFIG_INIT_OVERLAY,
			    g_options->config);

		dbrename(get_config_name(), DBG_SUBDIR_CLIENT);
	    }

	    /* check for any config errors now */
	    if (config_errors(&errlist) >= CFGERR_ERRORS) {
		char *errstr = config_errors_to_error_string(errlist);
		g_printf("%s\n", errstr);
		dbclose();
		return 1;
	    }

	    if (am_has_feature(g_options->features, fe_req_xml)) {
		break;
	    }
	    continue;
	}

	if (dle && dle->program != NULL) {
	    err_extra = _("multiple requests");
	    goto err;
	}

	dbprintf(_("  sendbackup req: <%s>\n"), line);
	dle = alloc_dle();

	s = line;
	ch = *s++;

	skip_whitespace(s, ch);			/* find the program name */
	if(ch == '\0') {
	    err_extra = _("no program name");
	    goto err;				/* no program name */
	}
	dle->program = s - 1;
	skip_non_whitespace(s, ch);
	s[-1] = '\0';

        if (g_str_equal(dle->program, "APPLICATION")) {
            dle->program_is_application_api=1;
            skip_whitespace(s, ch);             /* find dumper name */
            if (ch == '\0') {
                goto err;                       /* no program */
            }
            dle->program = s - 1;
            skip_non_whitespace(s, ch);
            s[-1] = '\0';
        }
	dle->program = g_strdup(dle->program);

	skip_whitespace(s, ch);			/* find the disk name */
	if(ch == '\0') {
	    err_extra = _("no disk name");
	    goto err;				/* no disk name */
	}

	amfree(qdisk);
	qdisk = s - 1;
	ch = *qdisk;
	skip_quoted_string(s, ch);
	s[-1] = '\0';
	qdisk = g_strdup(qdisk);
	dle->disk = unquote_string(qdisk);

	skip_whitespace(s, ch);			/* find the device or level */
	if (ch == '\0') {
	    err_extra = _("bad level");
	    goto err;
	}

	if(!isdigit((int)s[-1])) {
	    amfree(qamdevice);
	    qamdevice = s - 1;
	    ch = *qamdevice;
	    skip_quoted_string(s, ch);
	    s[-1] = '\0';
	    qamdevice = g_strdup(qamdevice);
	    dle->device = unquote_string(qamdevice);
	    skip_whitespace(s, ch);		/* find level number */
	}
	else {
	    dle->device = g_strdup(dle->disk);
	    qamdevice = g_strdup(qdisk);
	}
						/* find the level number */
	if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
	    err_extra = _("bad level");
	    goto err;				/* bad level */
	}
	skip_integer(s, ch);
	alevel = g_new0(am_level_t, 1);
	alevel->level = level;
	dle->levellist = g_slist_append(dle->levellist, alevel);

	skip_whitespace(s, ch);			/* find the dump date */
	if(ch == '\0') {
	    err_extra = _("no dumpdate");
	    goto err;				/* no dumpdate */
	}
	amfree(dumpdate);
	dumpdate = s - 1;
	skip_non_whitespace(s, ch);
	s[-1] = '\0';
	dumpdate = g_strdup(dumpdate);

	skip_whitespace(s, ch);			/* find the options keyword */
	if(ch == '\0') {
	    err_extra = _("no options");
	    goto err;				/* no options */
	}
	if(strncmp_const_skip(s - 1, "OPTIONS ", s, ch) != 0) {
	    err_extra = _("no OPTIONS keyword");
	    goto err;				/* no options */
	}
	skip_whitespace(s, ch);			/* find the options string */
	if(ch == '\0') {
	    err_extra = _("bad options string");
	    goto err;				/* no options */
	}
	amfree(stroptions);
	stroptions = g_strdup(s - 1);
    }
    amfree(line);
    if (g_options == NULL) {
	g_printf(_("ERROR [Missing OPTIONS line in sendbackup input]\n"));
	error(_("Missing OPTIONS line in sendbackup input\n"));
	/*NOTREACHED*/
    }
    if (am_has_feature(g_options->features, fe_req_xml)) {
	char *errmsg = NULL;

	dle = amxml_parse_node_FILE(stdin, &errmsg);
	if (errmsg) {
	    err_extra = errmsg;
	    goto err;
	}
	if (!dle) {
	    err_extra = _("One DLE required");
	    goto err;
	} else if (dle->next) {
	    err_extra = _("Only one DLE allowed");
	    goto err;
	}

	qdisk = quote_string(dle->disk);
	if (dle->device == NULL)
	    dle->device = g_strdup(dle->disk);
	qamdevice = quote_string(dle->device);
	dumpdate = g_strdup("NODATE");
	stroptions = g_strdup("");
    } else if (dle == NULL) {
	g_printf(_("ERROR [Missing DLE line in sendbackup input]\n"));
	error(_("Missing DLE line in sendbackup input\n"));
	/*NOTREACHED*/
    } else {
	parse_options(stroptions, dle, g_options->features, 0);
    }
    gdle = dle;

    if (dle->program   == NULL ||
	dle->disk      == NULL ||
	dle->device    == NULL ||
	dle->levellist == NULL ||
	dumpdate       == NULL) {
	err_extra = _("no valid sendbackup request");
	goto err;
    }

    if (g_slist_length(dle->levellist) != 1) {
	err_extra = _("Too many level");
	goto err;
    }

    alevel = (am_level_t *)dle->levellist->data;
    level = alevel->level;
    dbprintf(_("  Parsed request as: program `%s'\n"), dle->program);
    dbprintf(_("                     disk `%s'\n"), qdisk);
    dbprintf(_("                     device `%s'\n"), qamdevice);
    dbprintf(_("                     level %d\n"), level);
    dbprintf(_("                     since %s\n"), dumpdate);
    dbprintf(_("                     options `%s'\n"), stroptions);
    dbprintf(_("                     datapath `%s'\n"),
			    data_path_to_string(dle->data_path));

    if (dle->program_is_application_api==1) {
	/* check that the application_api exist */
    } else {
	for(i = 0; programs[i]; i++) {
	    if (g_str_equal(programs[i]->name, dle->program)) {
		break;
	    }
	}
	if (programs[i] == NULL) {
	    dbprintf(_("ERROR [%s: unknown program %s]\n"), get_pname(),
		     dle->program);
	    error(_("ERROR [%s: unknown program %s]"), get_pname(),
		  dle->program);
	    /*NOTREACHED*/
	}
	program = programs[i];
    }

    if(!interactive) {
	datafd = DATA_FD_OFFSET + 0;
	mesgfd = DATA_FD_OFFSET + 2;
	indexfd = DATA_FD_OFFSET + 4;
	statefd = DATA_FD_OFFSET + 6;
    }
    if (!dle->create_index) {
	if (!interactive) {
	    aclose(indexfd);
	    close(indexfd+1);
	}
	indexfd = -1;
    }

    if (dle->auth && amandad_auth) {
	if(strcasecmp(dle->auth, amandad_auth) != 0) {
	    g_printf(_("ERROR [client configured for auth=%s while server requested '%s']\n"),
		   amandad_auth, dle->auth);
	    exit(-1);
	}
    }

    if (dle->kencrypt) {
	g_printf("KENCRYPT\n");
    }

    if (am_has_feature(g_options->features, fe_sendbackup_stream_state)) {
	g_printf(_("CONNECT DATA %d MESG %d INDEX %d STATE %d\n"),
		   DATA_FD_OFFSET, DATA_FD_OFFSET+1,
		   indexfd == -1 ? -1 : DATA_FD_OFFSET+2,
		   DATA_FD_OFFSET+3);
    } else {
	g_printf(_("CONNECT DATA %d MESG %d INDEX %d\n"),
		   DATA_FD_OFFSET, DATA_FD_OFFSET+1,
		   indexfd == -1 ? -1 : DATA_FD_OFFSET+2);
	if (!interactive) {
	    aclose(statefd);
	    close(statefd+1);
	}
	statefd = -1;
    }
    g_printf(_("OPTIONS "));
    if(am_has_feature(g_options->features, fe_rep_options_features)) {
	g_printf("features=%s;", our_feature_string);
    }
    if(am_has_feature(g_options->features, fe_rep_options_hostname)) {
	g_printf("hostname=%s;", g_options->hostname);
    }
    if (!am_has_feature(g_options->features, fe_rep_options_features) &&
	!am_has_feature(g_options->features, fe_rep_options_hostname)) {
	g_printf(";");
    }
    g_printf("\n");
    fflush(stdout);
    if (freopen("/dev/null", "w", stdout) == NULL) {
	dbprintf(_("Error redirecting stdout to /dev/null: %s\n"),
		 strerror(errno));
        exit(1);
    }

    if(interactive) {
      if((datafd = open("/dev/null", O_RDWR)) < 0) {
	error(_("ERROR [open of /dev/null for debug data stream: %s]\n"),
		strerror(errno));
	/*NOTREACHED*/
      }
      mesgfd = 2;
      indexfd = 1;
    }

    if(!interactive) {
      if(datafd == -1 || mesgfd == -1 || (dle->create_index && indexfd == -1)) {
        dbclose();
        exit(1);
      }
    }

    if (merge_dles_properties(dle, 1) == 0) {
	g_debug("merge_dles_properties failed");
	exit(1);
    }
    mesgstream = fdopen(mesgfd,"w");
    if (mesgstream == NULL) {
	g_debug("Failed to fdopen mesgfd (%d): %s", mesgfd, strerror(errno));
	exit(1);
    }
    scripts_exit_status = run_client_scripts(EXECUTE_ON_PRE_DLE_BACKUP, g_options, dle, mesgstream, NULL);
    fflush(mesgstream);

    if (scripts_exit_status == 0) {
	if (dle->program_is_application_api==1) {
	    guint j;
	    char *cmd=NULL;
	    GPtrArray *argv_ptr;
	    char levelstr[20];
	    backup_support_option_t *bsu;
	    char *compopt = NULL;
	    char *encryptopt = skip_argument;
	    int compout, dumpout;
	    GSList    *scriptlist;
	    script_t  *script;
	    time_t     cur_dumptime;
	    int        result;
	    GPtrArray *errarray;
	    int        errfd[2];
	    FILE      *dumperr;
	    send_crc_t native_crc;
	    send_crc_t client_crc;
	    int        native_pipe[2];
	    int        client_pipe[2];
	    int        data_out = datafd;

	    crc32_init(&native_crc.crc);
	    crc32_init(&client_crc.crc);
	    /* create pipes to compute the native CRC */
	    if (pipe(native_pipe) < 0) {
		char  *errmsg;
		char  *qerrmsg;
		errmsg = g_strdup_printf("Application '%s': can't create pipe",
					 dle->program);
		qerrmsg = quote_string(errmsg);
		fdprintf(mesgfd, _("sendbackup: error [%s]\n"), errmsg);
		g_debug("ERROR %s", qerrmsg);
		amfree(qerrmsg);
		amfree(errmsg);
		return 0;
	    }

	    if (dle->encrypt == ENCRYPT_CUST ||
		dle->compress == COMP_FAST ||
		dle->compress == COMP_BEST ||
		dle->compress == COMP_CUST) {

		have_filter = TRUE;

		/* create pipes to compute the client CRC */
		if (pipe(client_pipe) < 0) {
		    char  *errmsg;
		    char  *qerrmsg;
		    errmsg = g_strdup_printf("Application '%s': can't create pipe",
					     dle->program);
		    qerrmsg = quote_string(errmsg);
		    fdprintf(mesgfd, _("sendbackup: error [%s]\n"), errmsg);
		    g_debug("ERROR %s", qerrmsg);
		    amfree(qerrmsg);
		    amfree(errmsg);
		    return 0;
		}
		data_out = client_pipe[1];
	    } else {
		data_out = datafd;
	    }


	   enc_stderr_pipe.fd = -1;
	   comp_stderr_pipe.fd = -1;
	    /*  apply client-side encryption here */
	    if ( dle->encrypt == ENCRYPT_CUST ) {
		encpid = pipespawn(dle->clnt_encrypt, STDIN_PIPE|STDERR_PIPE, 0,
				   &compout, &data_out, &enc_stderr_pipe.fd,
				   dle->clnt_encrypt, encryptopt, NULL);
		g_debug("encrypt: pid %ld: %s", (long)encpid, dle->clnt_encrypt);
	    } else {
		compout = data_out;
		encpid = -1;
	    }

	    /*  now do the client-side compression */
	    if(dle->compress == COMP_FAST || dle->compress == COMP_BEST) {
		compopt = skip_argument;
#if defined(COMPRESS_BEST_OPT) && defined(COMPRESS_FAST_OPT)
		if(dle->compress == COMP_BEST) {
		    compopt = COMPRESS_BEST_OPT;
		} else {
		    compopt = COMPRESS_FAST_OPT;
		}
#endif
		comppid = pipespawn(COMPRESS_PATH, STDIN_PIPE|STDERR_PIPE, 0,
				    &dumpout, &compout, &comp_stderr_pipe.fd,
				    COMPRESS_PATH, compopt, NULL);
		if(compopt != skip_argument) {
		    g_debug("compress pid %ld: %s %s",
			    (long)comppid, COMPRESS_PATH, compopt);
		} else {
		    g_debug("compress pid %ld: %s", (long)comppid, COMPRESS_PATH);
		}
		aclose(compout);
	    } else if (dle->compress == COMP_CUST) {
		compopt = skip_argument;
		comppid = pipespawn(dle->compprog, STDIN_PIPE|STDERR_PIPE, 0,
				&dumpout, &compout, &comp_stderr_pipe.fd,
				dle->compprog, compopt, NULL);
		if (compopt != skip_argument) {
		    g_debug("pid %ld: %s %s",
			    (long)comppid, dle->compprog, compopt);
		} else {
		    g_debug("pid %ld: %s", (long)comppid, dle->compprog);
		}
		aclose(compout);
	    } else {
		dumpout = compout;
		comppid = -1;
	    }

	    enc_stderr_pipe.thread = NULL;
	    if (enc_stderr_pipe.fd != -1) {
		enc_stderr_pipe.thread = g_thread_create(stderr_thread,
					(gpointer)&enc_stderr_pipe , TRUE, NULL);
	    }

	    comp_stderr_pipe.thread = NULL;
	    if (comp_stderr_pipe.fd != -1) {
		comp_stderr_pipe.thread = g_thread_create(stderr_thread,
					(gpointer)&comp_stderr_pipe , TRUE, NULL);
	    }

	    cur_dumptime = time(0);
	    bsu = backup_support_option(dle->program, &errarray);
	    if (!bsu) {
		char  *errmsg;
		char  *qerrmsg;
		guint  i;
		for (i=0; i < errarray->len; i++) {
		    errmsg = g_ptr_array_index(errarray, i);
		    qerrmsg = quote_string(errmsg);
		    fdprintf(mesgfd,
			  "sendbackup: error [Application '%s': %s]\n",
			  dle->program, errmsg);
		    g_debug("ERROR %s",qerrmsg);
		    amfree(qerrmsg);
		}
		if (i == 0) { /* no errarray */
		    errmsg = g_strdup_printf("Can't execute application '%s'",
					     dle->program);
		    qerrmsg = quote_string(errmsg);
		    fdprintf(mesgfd, "sendbackup: error [%s]\n", errmsg);
		    g_debug("ERROR %s", qerrmsg);
		    amfree(qerrmsg);
		    amfree(errmsg);
		}
		g_ptr_array_free_full(errarray);
		return 0;
	    }

	    if (pipe(errfd) < 0) {
		char  *errmsg;
		char  *qerrmsg;
		errmsg = g_strdup_printf("Application '%s': can't create pipe",
					 dle->program);
		qerrmsg = quote_string(errmsg);
		fdprintf(mesgfd, "sendbackup: error [%s]\n", errmsg);
		g_debug("ERROR %s", qerrmsg);
		amfree(qerrmsg);
		amfree(errmsg);
		return 0;
	    }

	    application_api_info_tapeheader(mesgfd, dle->program, dle);

	    switch(application_api_pid=fork()) {
	    case 0:

		/* find directt-tcp address from indirect direct-tcp */
		if (dle->data_path == DATA_PATH_DIRECTTCP &&
		    bsu->data_path_set & DATA_PATH_DIRECTTCP &&
		    strncmp(dle->directtcp_list->data, "255.255.255.255:", 16) == 0) {
		    char *indirect_tcp;
		    char *str_port;
		    in_port_t port;
		    int fd;
		    char buffer[32770];
		    int size;
		    char *s, *s1;
		    char *stream_msg = NULL;

		    indirect_tcp = g_strdup(dle->directtcp_list->data);
		    g_debug("indirecttcp: %s", indirect_tcp);
		    g_slist_free(dle->directtcp_list);
		    dle->directtcp_list = NULL;
		    str_port = strchr(indirect_tcp, ':');
		    if (str_port == NULL) {
			g_debug("Invalid indirect_tcp: %s", indirect_tcp);
			exit(1);
		    }
		    str_port++;
		    port = atoi(str_port);
		    fd = stream_client(NULL, "localhost", port, 32768, 32768, NULL, 0, &stream_msg);
		    if (stream_msg) {
			g_debug("Failed to connect to indirect-direct-tcp port: %s",
				stream_msg);
			g_free(stream_msg);
			exit(1);
		    }
		    if (fd <= 0) {
			g_debug("Failed to connect to indirect-direct-tcp port: %s",
				strerror(errno));
			exit(1);
		    }
		    size = full_read(fd, buffer, 32768);
		    if (size <= 0) {
			g_debug("Failed to read from indirect-direct-tcp port: %s",
				strerror(errno));
			close(fd);
			exit(1);
		    }
		    close(fd);
		    buffer[size++] = ' ';
		    buffer[size] = '\0';
		    s1 = buffer;
		    while ((s = strchr(s1, ' ')) != NULL) {
			*s++ = '\0';
			g_debug("directtcp: %s", s1);
			dle->directtcp_list = g_slist_append(dle->directtcp_list, g_strdup(s1));
			s1 = s;
		    }
		    amfree(indirect_tcp);
		}

		argv_ptr = g_ptr_array_new();
		cmd = g_strjoin(NULL, APPLICATION_DIR, "/", dle->program, NULL);
		g_ptr_array_add(argv_ptr, g_strdup(dle->program));
		g_ptr_array_add(argv_ptr, g_strdup("backup"));
		if (bsu->message_line == 1) {
		    g_ptr_array_add(argv_ptr, g_strdup("--message"));
		    g_ptr_array_add(argv_ptr, g_strdup("line"));
		}
		if (g_options->config && bsu->config == 1) {
		    g_ptr_array_add(argv_ptr, g_strdup("--config"));
		    g_ptr_array_add(argv_ptr, g_strdup(g_options->config));
		}
		if (g_options->timestamp && bsu->timestamp == 1) {
		    g_ptr_array_add(argv_ptr, g_strdup("--timestamp"));
		    g_ptr_array_add(argv_ptr, g_strdup(g_options->timestamp));
		}
		if (g_options->hostname && bsu->host == 1) {
		    g_ptr_array_add(argv_ptr, g_strdup("--host"));
		    g_ptr_array_add(argv_ptr, g_strdup(g_options->hostname));
		}
		if (dle->disk && bsu->disk == 1) {
		    g_ptr_array_add(argv_ptr, g_strdup("--disk"));
		    g_ptr_array_add(argv_ptr, g_strdup(dle->disk));
		}
		g_ptr_array_add(argv_ptr, g_strdup("--device"));
		g_ptr_array_add(argv_ptr, g_strdup(dle->device));
		if (level <= bsu->max_level) {
		    g_ptr_array_add(argv_ptr, g_strdup("--level"));
		    g_snprintf(levelstr,19,"%d",level);
		    g_ptr_array_add(argv_ptr, g_strdup(levelstr));
		}
		if (indexfd != -1 && bsu->index_line == 1) {
		    g_ptr_array_add(argv_ptr, g_strdup("--index"));
		    g_ptr_array_add(argv_ptr, g_strdup("line"));
		}
		if (statefd != -1 && bsu->state_stream == 1) {
		    g_ptr_array_add(argv_ptr, g_strdup("--state-stream"));
		    g_ptr_array_add(argv_ptr, g_strdup_printf("%d", statefd));
		}
		if (dle->record && bsu->record == 1) {
		    g_ptr_array_add(argv_ptr, g_strdup("--record"));
		}
		application_property_add_to_argv(argv_ptr, dle, bsu,
						 g_options->features);

		for (scriptlist = 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);
		    }
		}

		g_ptr_array_add(argv_ptr, NULL);
		g_debug("%s: running \"%s", get_pname(), cmd);
		for (j = 1; j < argv_ptr->len - 1; j++)
		    g_debug(" %s", (char *)g_ptr_array_index(argv_ptr,j));
		g_debug("\"");
		if (dup2(native_pipe[1], 1) == -1) {
		    error(_("native: Can't dup2: %s"),strerror(errno));
		    /*NOTREACHED*/
		}
		if (dup2(errfd[1], 2) == -1) {
		    error(_("errfd: Can't dup2: %s"),strerror(errno));
		    /*NOTREACHED*/
		}
		if (dup2(mesgfd, 3) == -1) {
		    error(_("mesgfd: Can't dup2: %s"),strerror(errno));
		    /*NOTREACHED*/
		}
		if (indexfd > 0) {
		    if (dup2(indexfd, 4) == -1) {
			error(_("indexfd: Can't dup2: %s"),strerror(errno));
			/*NOTREACHED*/
		    }
		    fcntl(indexfd, F_SETFD, 0);
		}
		if (indexfd != 0) {
		    safe_fd2(3, 2, statefd);
		} else {
		    safe_fd2(3, 1, statefd);
		}
		env = safe_env();
		execve(cmd, (char **)argv_ptr->pdata, env);
		free_env(env);
		exit(1);
		break;

	default:
		break;
	case -1:
		error(_("%s: fork returned: %s"), get_pname(), strerror(errno));
	}

	    close(errfd[1]);
	    dumperr = fdopen(errfd[0],"r");
	    if (!dumperr) {
		error(_("Can't fdopen: %s"), strerror(errno));
		/*NOTREACHED*/
	    }

	    close(native_pipe[1]);
	    if (shm_control_name) {
		shm_ring = shm_ring_link(shm_control_name);
		shm_ring_producer_set_size(shm_ring, NETWORK_BLOCK_BYTES*16, NETWORK_BLOCK_BYTES*4);
		native_crc.in  = native_pipe[0];
		if (!have_filter) {
		    native_crc.shm_ring = shm_ring;
		    native_crc.out = dumpout;
		    native_crc.thread = g_thread_create(handle_crc_to_shm_ring_thread,
					 (gpointer)&native_crc, TRUE, NULL);
		} else {
		    native_crc.out = dumpout;
		    native_crc.thread = g_thread_create(handle_crc_thread,
					 (gpointer)&native_crc, TRUE, NULL);
		    close(client_pipe[1]);
		    client_crc.shm_ring = shm_ring;
		    client_crc.in  = client_pipe[0];
		    client_crc.out = datafd;
		    client_crc.thread = g_thread_create(handle_crc_to_shm_ring_thread,
					 (gpointer)&client_crc, TRUE, NULL);
		}
	    } else {
		native_crc.in  = native_pipe[0];
		native_crc.out = dumpout;
		native_crc.thread = g_thread_create(handle_crc_thread,
					(gpointer)&native_crc, TRUE, NULL);

		if (have_filter) {
		    close(client_pipe[1]);
		    client_crc.in  = client_pipe[0];
		    client_crc.out = datafd;
		    client_crc.thread = g_thread_create(handle_crc_thread,
					(gpointer)&client_crc, TRUE, NULL);
		}

	    }

	    if (statefd >= 0 && !bsu->state_stream) {
		aclose(statefd);
		close(statefd+1);
		statefd = -1;
	    }

	    result = 0;
	    while ((line = pgets(dumperr)) != NULL) {
		if (strlen(line) > 0) {
		    fdprintf(mesgfd, "sendbackup: error [%s]\n", line);
		    g_debug("error: %s", line);
		    result = 1;
		}
		amfree(line);
	    }
	    g_thread_join(native_crc.thread);
	    if (have_filter) {
		if (enc_stderr_pipe.thread) {
		    g_thread_join(enc_stderr_pipe.thread);
		}
		if (comp_stderr_pipe.thread) {
		    g_thread_join(comp_stderr_pipe.thread);
		}
		g_thread_join(client_crc.thread);
	    }

	    if (shm_ring) {
		close_producer_shm_ring(shm_ring);
		shm_ring = NULL;
	    }

	    result |= check_result(mesgfd);
	    if (result == 0) {
		char *amandates_file;

		amandates_file = getconf_str(CNF_AMANDATES);
		if (start_amandates(amandates_file, 1)) {
		    amandates_updateone(dle->disk, level, cur_dumptime);
		    finish_amandates();
		    free_amandates();
		} else {
		    if (GPOINTER_TO_INT(dle->estimatelist->data) == ES_CALCSIZE &&
		    bsu->calcsize) {
		    error(_("error [opening %s for writing: %s]"),
			  amandates_file, strerror(errno));
		    } else {
		    g_debug(_("non-fatal error opening '%s' for writing: %s]"),
			    amandates_file, strerror(errno));
		    }
		}
	    }

	    if (!have_filter)
		client_crc.crc = native_crc.crc;

	    if (am_has_feature(g_options->features, fe_sendbackup_crc)) {
		g_debug("sendbackup: native-CRC %08x:%lld",
			crc32_finish(&native_crc.crc),
			(long long)native_crc.crc.size);
		g_debug("sendbackup: client-CRC %08x:%lld",
			crc32_finish(&client_crc.crc),
			(long long)client_crc.crc.size);
		fprintf(mesgstream, "sendbackup: native-CRC %08x:%lld\n",
			crc32_finish(&native_crc.crc),
			(long long)native_crc.crc.size);
		fprintf(mesgstream, "sendbackup: client-CRC %08x:%lld\n",
			crc32_finish(&client_crc.crc),
			(long long)client_crc.crc.size);
	    }

	    g_debug("sendbackup: end");
	    fprintf(mesgstream, "sendbackup: end\n");

	    amfree(bsu);
	} else {
	    aclose(statefd);
	    close(statefd+1);
	    if(!interactive) {
		/* redirect stderr */
		if (dup2(mesgfd, 2) == -1) {
		    g_debug("Error redirecting stderr to fd %d: %s",
			     mesgfd, strerror(errno));
		    dbclose();
		    exit(1);
		}
	    }

	    if (pipe(mesgpipe) == -1) {
		s = strerror(errno);
			    dbprintf(_("error [opening mesg pipe: %s]\n"), s);
			    error(_("error [opening mesg pipe: %s]"), s);
	    }

	    program->start_backup(dle, g_options->hostname,
				  datafd, mesgpipe[1], indexfd);
	    g_debug("Started backup");
	    parse_backup_messages(dle, mesgpipe[0]);
	    g_debug("Parsed backup messages");
	}
    } else {
	if (shm_ring) {
	    shm_ring->mc->cancelled = TRUE;
	    sem_post(shm_ring->sem_ready);
	    sem_post(shm_ring->sem_start);
	    sem_post(shm_ring->sem_write);
	    sem_post(shm_ring->sem_read);
	    close_producer_shm_ring(shm_ring);
	    shm_ring = NULL;
	}
    }

    run_client_scripts(EXECUTE_ON_POST_DLE_BACKUP, g_options, dle, mesgstream, NULL);
    fflush(mesgstream);
    fclose(mesgstream);

    amfree(qdisk);
    amfree(qamdevice);
    amfree(dumpdate);
    amfree(stroptions);
    amfree(our_feature_string);
    am_release_feature_set(our_features);
    our_features = NULL;
    free_g_options(g_options);

    dbclose();

    return 0;

 err:
    if (err_extra) {
	g_printf(_("ERROR FORMAT ERROR IN REQUEST PACKET '%s'\n"), err_extra);
	dbprintf(_("REQ packet is bogus: %s\n"), err_extra);
    } else {
	g_printf(_("ERROR FORMAT ERROR IN REQUEST PACKET\n"));
	dbprintf(_("REQ packet is bogus\n"));
    }

    amfree(qdisk);
    amfree(qamdevice);
    amfree(dumpdate);
    amfree(stroptions);
    amfree(our_feature_string);

    dbclose();
    return 1;
}


/*
 * Returns a string for a child process.  Checks the saved dump and
 * compress pids to see which it is.
 */

char *
childstr(
    pid_t pid)
{
    if (pid == comppid) return "compress";
    if (pid == encpid) return "encrypt";
    if (pid == indexpid) return "index";
    if (pid == application_api_pid) {
	if (!gdle) {
	    g_debug("gdle == NULL");
	    return "gdle == NULL";
	}
	return gdle->program;
    }
    if (pid == dumppid) {
	if (!program) {
	    g_debug("program == NULL");
	    return "program == NULL";
	}
	return program->backup_name;
    }
    return "unknown";
}


/*
 * Determine if the child return status really indicates an error.
 * If so, add the error message to the error string; more than one
 * child can have an error.
 */

int
check_status(
    pid_t	pid,
    amwait_t	w,
    int		mesgfd)
{
    char *tmpbuf;
    char *thiserr = NULL;
    char *str, *strX;
    int ret, sig, rc;

    str = childstr(pid);

    if(WIFSIGNALED(w)) {
	ret = 0;
	rc = sig = WTERMSIG(w);
    } else {
	sig = 0;
	rc = ret = WEXITSTATUS(w);
    }

    if(pid == indexpid) {
	/*
	 * Treat an index failure (other than signal) as a "STRANGE"
	 * rather than an error so the dump goes ahead and gets processed
	 * but the failure is noted.
	 */
	if(ret != 0) {
	    fdprintf(mesgfd, _("? index %s returned %d\n"), str, ret);
	    rc = 0;
	}
	indexpid = -1;
	strX = "index";
    } else if(pid == comppid) {
	/*
	 * compress returns 2 sometimes, but it is ok.
	 */
#ifndef HAVE_GZIP
	if(ret == 2) {
	    rc = 0;
	}
#endif
	comppid = -1;
	strX = "compress";
    } else if(pid == encpid) {
	encpid = -1;
	strX = "encrypt";
    } else if(pid == dumppid && tarpid == -1) {
        /*
	 * Ultrix dump returns 1 sometimes, but it is ok.
	 */
#ifdef DUMP_RETURNS_1
        if(ret == 1) {
	    rc = 0;
	}
#endif
	dumppid = -1;
	strX = "dump";
    } else if(pid == tarpid) {
	if (ret == 1) {
	    rc = 0;
	}
	/*
	 * tar bitches about active filesystems, but we do not care.
	 */
#ifdef IGNORE_TAR_ERRORS
        if(ret == 2) {
	    rc = 0;
	}
#endif
	dumppid = tarpid = -1;
	strX = "dump";
    } else if(pid == application_api_pid) {
	if (ret == 1) {
	    rc = 0;
	}
	application_api_pid = -1;
	strX = "Application";
    } else {
	strX = "unknown";
    }

    if(rc == 0) {
	return 0;				/* normal exit */
    }

    if(ret == 0) {
	thiserr = g_strdup_printf(_("%s (%d) %s got signal %d"), strX, (int)pid, str,
			     sig);
    } else {
	thiserr = g_strdup_printf(_("%s (%d) %s returned %d"), strX, (int)pid, str, ret);
    }

    fdprintf(mesgfd, "sendbackup: error [%s]\n", thiserr);
    g_debug("sendbackup: error [%s]", thiserr);

    if(errorstr) {
	tmpbuf = g_strdup_printf("%s, %s", errorstr, thiserr);
	g_free(errorstr);
	errorstr = tmpbuf;
	amfree(thiserr);
    } else {
	errorstr = thiserr;
	thiserr = NULL;
    }
    return 1;
}


/*
 *Send header info to the message file.
 */
void
info_tapeheader(
    dle_t *dle)
{
    g_fprintf(stderr, "%s: info BACKUP=%s\n", get_pname(), program->backup_name);

    g_fprintf(stderr, "%s: info RECOVER_CMD=", get_pname());
    if (dle->compress == COMP_FAST || dle->compress == COMP_BEST)
	g_fprintf(stderr, "%s %s |", UNCOMPRESS_PATH,
#ifdef UNCOMPRESS_OPT
		UNCOMPRESS_OPT
#else
		""
#endif
		);

    g_fprintf(stderr, "%s -xpGf - ...\n", program->restore_name);

    if (dle->compress == COMP_FAST || dle->compress == COMP_BEST)
	g_fprintf(stderr, "%s: info COMPRESS_SUFFIX=%s\n",
			get_pname(), COMPRESS_SUFFIX);

    g_fprintf(stderr, "%s: info end\n", get_pname());
}

void
application_api_info_tapeheader(
    int       mesgfd,
    char     *prog,
    dle_t *dle)
{
    char line[1024];

    g_snprintf(line, 1024, "%s: info BACKUP=APPLICATION\n", get_pname());
    if (full_write(mesgfd, line, strlen(line)) != strlen(line)) {
	dbprintf(_("error writing to mesgfd socket (%d): %s"), mesgfd, strerror(errno));
	return;
    }

    g_snprintf(line, 1024, "%s: info APPLICATION=%s\n", get_pname(), prog);
    if (full_write(mesgfd, line, strlen(line)) != strlen(line)) {
	dbprintf(_("error writing to mesgfd socket (%d): %s"), mesgfd, strerror(errno));
	return;
    }

    g_snprintf(line, 1024, "%s: info RECOVER_CMD=", get_pname());
    if (full_write(mesgfd, line, strlen(line)) != strlen(line)) {
	dbprintf(_("error writing to mesgfd socket (%d): %s"), mesgfd, strerror(errno));
	return;
    }

    if (dle->compress == COMP_FAST || dle->compress == COMP_BEST) {
	g_snprintf(line, 1024, "%s %s |", UNCOMPRESS_PATH,
#ifdef UNCOMPRESS_OPT
		 UNCOMPRESS_OPT
#else
		 ""
#endif
		 );
	if (full_write(mesgfd, line, strlen(line)) != strlen(line)) {
	    dbprintf(_("error writing to mesgfd socket (%d): %s"), mesgfd, strerror(errno));
	    return;
	}
    }
    g_snprintf(line, 1024, "%s/%s restore [./file-to-restore]+\n",
	       APPLICATION_DIR, prog);
    if (full_write(mesgfd, line, strlen(line)) != strlen(line)) {
	dbprintf(_("error writing to mesgfd socket (%d): %s"), mesgfd, strerror(errno));
	return;
    }

    if (dle->compress) {
	g_snprintf(line, 1024, "%s: info COMPRESS_SUFFIX=%s\n",
		 get_pname(), COMPRESS_SUFFIX);
	if (full_write(mesgfd, line, strlen(line)) != strlen(line)) {
	    dbprintf(_("error writing to mesgfd socket (%d): %s"), mesgfd, strerror(errno));
	    return;
	}
    }

    g_snprintf(line, 1024, "%s: info end\n", get_pname());
    if (full_write(mesgfd, line, strlen(line)) != strlen(line)) {
	dbprintf(_("error writing to mesgfd socket (%d): %s"), mesgfd, strerror(errno));
	return;
    }
}

pid_t
pipefork(
    void	(*func)(void),
    char *	fname,
    int *	stdinfd,
    int		stdoutfd,
    int		stderrfd)
{
    int inpipe[2];
    pid_t pid;

    dbprintf(_("Forking function %s in pipeline\n"), fname);

    if(pipe(inpipe) == -1) {
	error(_("error [open pipe to %s: %s]"), fname, strerror(errno));
	/*NOTREACHED*/
    }

    switch(pid = fork()) {
    case -1:
	error(_("error [fork %s: %s]"), fname, strerror(errno));
	/*NOTREACHED*/
    default:	/* parent process */
	aclose(inpipe[0]);	/* close input side of pipe */
	*stdinfd = inpipe[1];
	break;
    case 0:		/* child process */
	aclose(inpipe[1]);	/* close output side of pipe */

	if(dup2(inpipe[0], 0) == -1) {
	    error(_("error [fork %s: dup2(%d, in): %s]"),
		  fname, inpipe[0], strerror(errno));
	    /*NOTRACHED*/
	}
	if(dup2(stdoutfd, 1) == -1) {
	    error(_("error [fork %s: dup2(%d, out): %s]"),
		  fname, stdoutfd, strerror(errno));
	    /*NOTRACHED*/
	}
	if(dup2(stderrfd, 2) == -1) {
	    error(_("error [fork %s: dup2(%d, err): %s]"),
		  fname, stderrfd, strerror(errno));
	    /*NOTRACHED*/
	}

	func();
	exit(0);
	/*NOTREACHED*/
    }
    return pid;
}

int
check_result(
    int mesgfd)
{
    int goterror;
    pid_t wpid;
    amwait_t retstat;
    int process_alive = 1;
    int count = 0;

    goterror = 0;

    while (process_alive && count < 600) {
	process_alive = 0;
	if (indexpid != -1) {
	    if ((wpid = waitpid(indexpid, &retstat, WNOHANG)) > 0) {
		if (check_status(wpid, retstat, mesgfd)) goterror = 1;
		indexpid = -1;
	    } else if (wpid == 0) {
		process_alive = 1;
	    }
	}
	if (encpid != -1) {
	    if ((wpid = waitpid(encpid, &retstat, WNOHANG)) > 0) {
		if (check_status(wpid, retstat, mesgfd)) goterror = 1;
		encpid = -1;
	    } else if (wpid == 0) {
		process_alive = 1;
	    }
	}
	if (comppid != -1) {
	    if ((wpid = waitpid(comppid, &retstat, WNOHANG)) > 0) {
		if (check_status(wpid, retstat, mesgfd)) goterror = 1;
		comppid = -1;
	    } else if (wpid == 0) {
		process_alive = 1;
	    }
	}
	if (dumppid != -1) {
	    if ((wpid = waitpid(dumppid, &retstat, WNOHANG)) > 0) {
		if (check_status(wpid, retstat, mesgfd)) goterror = 1;
		dumppid = -1;
	    } else if (wpid == 0) {
		process_alive = 1;
	    }
	}
	if (tarpid != -1) {
	    if ((wpid = waitpid(tarpid, &retstat, WNOHANG)) > 0) {
		if (check_status(wpid, retstat, mesgfd)) goterror = 1;
		tarpid = -1;
	    } else if (wpid == 0) {
		process_alive = 1;
	    }
	}
	if (application_api_pid != -1) {
	    if ((wpid = waitpid(application_api_pid, &retstat, WNOHANG)) > 0) {
		if (check_status(wpid, retstat, mesgfd)) goterror = 1;
	    } else if (wpid == 0) {
		process_alive = 1;
	    }
	}

	while ((wpid = waitpid(-1, &retstat, WNOHANG)) > 0) {
	    if (check_status(wpid, retstat, mesgfd)) goterror = 1;
	}
	if (wpid == 0)
	    process_alive = 1;

	if (process_alive) {
	    sleep(1);
	    count++;
	}
    }

    if (dumppid == -1 && tarpid != -1)
	dumppid = tarpid;
    if (dumppid == -1 && application_api_pid != -1)
	dumppid = application_api_pid;

    if (dumppid != -1) {
	dbprintf(_("Sending SIGHUP to dump process %d\n"),
		  (int)dumppid);
	if(dumppid != -1) {
	    if(kill(dumppid, SIGHUP) == -1) {
		dbprintf(_("Can't send SIGHUP to %d: %s\n"),
			  (int)dumppid,
			  strerror(errno));
	    }
	}
	sleep(5);
	while((wpid = waitpid((pid_t)-1, &retstat, WNOHANG)) > 0) {
	    if(check_status(wpid, retstat, mesgfd)) goterror = 1;
	}
    }
    if (dumppid != -1) {
	dbprintf(_("Sending SIGKILL to dump process %d\n"),
		  (int)dumppid);
	if(dumppid != -1) {
	    if(kill(dumppid, SIGKILL) == -1) {
		dbprintf(_("Can't send SIGKILL to %d: %s\n"),
			  (int)dumppid,
			  strerror(errno));
	    }
	}
	sleep(5);
	while((wpid = waitpid((pid_t)-1, &retstat, WNOHANG)) > 0) {
	    if(check_status(wpid, retstat, mesgfd)) goterror = 1;
	}
    }

    return goterror;
}

void
parse_backup_messages(
    dle_t      *dle,
    int		mesgin)
{
    int goterror;
    char *line;

    amfree(errorstr);

    for(; (line = areads(mesgin)) != NULL; free(line)) {
	process_dumpline(line);
    }

    if(errno) {
	error(_("error [read mesg pipe: %s]"), strerror(errno));
	/*NOTREACHED*/
    }

    goterror = check_result(mesgfd);

    if(errorstr) {
	error(_("error [%s]"), errorstr);
	/*NOTREACHED*/
    } else if(dump_size == -1) {
	error(_("error [no backup size line]"));
	/*NOTREACHED*/
    }

    g_thread_join(native_crc.thread);
    if (have_filter) {
	g_thread_join(client_crc.thread);
	if (enc_stderr_pipe.thread) {
	    g_thread_join(enc_stderr_pipe.thread);
	}
	if (comp_stderr_pipe.thread) {
	    g_thread_join(comp_stderr_pipe.thread);
	}
    }

    if (shm_ring) {
	close_producer_shm_ring(shm_ring);
	shm_ring = NULL;
    }

    if (!have_filter)
	client_crc.crc = native_crc.crc;

    if (am_has_feature(g_options->features, fe_sendbackup_crc)) {
	dbprintf("sendbackup: native-CRC %08x:%lld\n",
		crc32_finish(&native_crc.crc),
		(long long)native_crc.crc.size);
	dbprintf("sendbackup: client-CRC %08x:%lld\n",
		crc32_finish(&client_crc.crc),
		(long long)client_crc.crc.size);
	fdprintf(mesgfd, "sendbackup: native-CRC %08x:%lld\n",
		crc32_finish(&native_crc.crc),
		(long long)native_crc.crc.size);
	fdprintf(mesgfd, "sendbackup: client-CRC %08x:%lld\n",
		crc32_finish(&client_crc.crc),
		(long long)client_crc.crc.size);
    }

    program->end_backup(dle, goterror);
    fdprintf(mesgfd, _("%s: size %ld\n"), get_pname(), dump_size);
    fdprintf(mesgfd, _("%s: end\n"), get_pname());
}


static void
process_dumpline(
    char *	str)
{
    amregex_t *rp;
    char *type;
    char startchr;

    for(rp = program->re_table; rp->regex != NULL; rp++) {
	if(match(rp->regex, str)) {
	    break;
	}
    }
    if(rp->typ == DMP_SIZE) {
	dump_size = (long)((the_num(str, rp->field)* rp->scale+1023.0)/1024.0);
    }
    switch(rp->typ) {
    case DMP_NORMAL:
	type = "normal";
	startchr = '|';
	break;
    case DMP_STRANGE:
	type = "strange";
	startchr = '?';
	break;
    case DMP_SIZE:
	type = "size";
	startchr = '|';
	break;
    case DMP_ERROR:
	type = "error";
	startchr = '?';
	break;
    default:
	/*
	 * Should never get here.
	 */
	type = "unknown";
	startchr = '!';
	break;
    }
    dbprintf("%3d: %7s(%c): %s\n",
	      rp->srcline,
	      type,
	      startchr,
	      str);
    fdprintf(mesgfd, "%c %s\n", startchr, str);
}


/*
 * start_index.  Creates an index file from the output of dump/tar.
 * It arranges that input is the fd to be written by the dump process.
 * If createindex is not enabled, it does nothing.  If it is not, a
 * new process will be created that tees input both to a pipe whose
 * read fd is dup2'ed input and to a program that outputs an index
 * file to `index'.
 *
 * make sure that the chat from restore doesn't go to stderr cause
 * this goes back to amanda which doesn't expect to see it
 * (2>/dev/null should do it)
 *
 * Originally by Alan M. McIvor, 13 April 1996
 *
 * Adapted by Alexandre Oliva, 1 May 1997
 *
 * This program owes a lot to tee.c from GNU sh-utils and dumptee.c
 * from the DeeJay backup package.
 */

static void
save_fd(
    int *	fd,
    int		min)
{
  int origfd = *fd;

  while (*fd >= 0 && *fd < min) {
    int newfd = dup(*fd);
    if (newfd == -1)
      dbprintf(_("Unable to save file descriptor [%s]\n"), strerror(errno));
    *fd = newfd;
  }
  if (origfd != *fd)
    dbprintf(_("Dupped file descriptor %i to %i\n"), origfd, *fd);
}

void
start_index(
    int		createindex,
    int		input,
    int		mesg,
    int		index,
    char *	cmd)
{
  int pipefd[2];
  FILE *pipe_fp;
  int exitcode;

  if (!createindex)
    return;

  if (pipe(pipefd) != 0) {
    error(_("creating index pipe: %s"), strerror(errno));
    /*NOTREACHED*/
  }

  switch(indexpid = fork()) {
  case -1:
    error(_("forking index tee process: %s"), strerror(errno));
    /*NOTREACHED*/

  default:
    aclose(pipefd[0]);
    if (dup2(pipefd[1], input) == -1) {
      error(_("dup'ping index tee output: %s"), strerror(errno));
      /*NOTREACHED*/
    }
    aclose(pipefd[1]);
    return;

  case 0:
    break;
  }

  /* now in a child process */
  save_fd(&pipefd[0], 4);
  save_fd(&index, 4);
  save_fd(&mesg, 4);
  save_fd(&input, 4);
  dup2(pipefd[0], 0);
  if (index != 1) {
    dup2(index, 1);
    aclose(index);
  }
  if (mesg != 2) {
    dup2(mesg, 2);
    aclose(mesg);
  }
  if (input != 3) {
    dup2(input, 3);
    aclose(input);
  }
  for(index = 4; index < (int)FD_SETSIZE; index++) {
    if (index != dbfd()) {
      close(index);
    }
  }

  if ((pipe_fp = popen(cmd, "w")) == NULL) {
    error(_("couldn't start index creator [%s]"), strerror(errno));
    /*NOTREACHED*/
  }

  dbprintf(_("Started index creator: \"%s\"\n"), cmd);
  while(1) {
    char buffer[BUFSIZ];
    ssize_t bytes_read;
    size_t just_written;

    do {
	bytes_read = read(0, buffer, sizeof(buffer));
    } while ((bytes_read < 0) && ((errno == EINTR) || (errno == EAGAIN)));

    if (bytes_read < 0) {
      error(_("index tee cannot read [%s]"), strerror(errno));
      /*NOTREACHED*/
    }

    if (bytes_read == 0)
      break; /* finished */

    /* write the stuff to the subprocess */
    just_written = full_write(fileno(pipe_fp), buffer, (size_t)bytes_read);
    if (just_written < (size_t)bytes_read) {
	/*
	 * just as we waited for write() to complete.
	 */
	if (errno != EPIPE) {
	    dbprintf(_("Index tee cannot write to index creator [%s]\n"),
			    strerror(errno));
	}
    }

    /* write the stuff to stdout, ensuring none lost when interrupt
       occurs */
    just_written = full_write(3, buffer, bytes_read);
    if (just_written < (size_t)bytes_read) {
	error(_("index tee cannot write [%s]"), strerror(errno));
	/*NOTREACHED*/
    }
  }

  aclose(pipefd[1]);

  /* finished */
  /* check the exit code of the pipe and moan if not 0 */
  if ((exitcode = pclose(pipe_fp)) != 0) {
    char *exitstr = str_exit_status("Index pipe", exitcode);
    dbprintf("%s\n", exitstr);
    amfree(exitstr);
  } else {
    dbprintf(_("Index created successfully\n"));
  }
  pipe_fp = NULL;

  close(0);
  close(1);
  close(2);
  close(3);
  close(dbfd());
  exit(exitcode);
}

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;
}


gpointer
stderr_thread(
    gpointer data)
{
    filter_stderr_pipe *fsp = (filter_stderr_pipe *)data;
    char *buf;

    while ((buf = areads(fsp->fd)) != NULL) {
	if (shm_ring) {
	    shm_ring->mc->cancelled = TRUE;
	    sem_post(shm_ring->sem_ready);
	    sem_post(shm_ring->sem_start);
	    sem_post(shm_ring->sem_write);
	    sem_post(shm_ring->sem_read);
	}
	if (strncmp(buf, "sendbackup: error [", 19) == 0) {
	    fdprintf(mesgfd, "%s\n", buf);
	} else {
	    fdprintf(mesgfd, "sendbackup: error [%s]\n", buf);
	}
	g_debug("error [%s]\n", buf);
	g_free(buf);
    }
    aaclose(fsp->fd);
    return NULL;
}

gpointer
handle_crc_to_shm_ring_thread(
    gpointer data)
{
    send_crc_t *crc = (send_crc_t *)data;

    fd_to_shm_ring(crc->in, crc->shm_ring, &crc->crc);

    close(crc->in);
    close(crc->out);

    return NULL;
}


extern backup_program_t dump_program, gnutar_program;

backup_program_t *programs[] = {
  &dump_program, &gnutar_program, NULL
};
