Imported Upstream version 2.6.1
[debian/amanda] / application-src / amgtar.c
diff --git a/application-src/amgtar.c b/application-src/amgtar.c
new file mode 100644 (file)
index 0000000..c3e5966
--- /dev/null
@@ -0,0 +1,1214 @@
+/*
+ * Amanda, The Advanced Maryland Automatic Network Disk Archiver
+ * Copyright (c) 1991-1998 University of Maryland at College Park
+ * All Rights Reserved.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of U.M. not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  U.M. makes no representations about the
+ * suitability of this software for any purpose.  It is provided "as is"
+ * without express or implied warranty.
+ *
+ * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
+ * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors: the Amanda Development Team.  Its members are listed in a
+ * file named AUTHORS, in the root directory of this distribution.
+ */
+/* 
+ * $Id: amgtar.c 8888 2007-10-02 13:40:42Z martineau $
+ *
+ * send estimated backup sizes using dump
+ */
+
+/* PROPERTY:
+ *
+ * GNUTAR-PATH     (default GNUTAR)
+ * GNUTAR-LISTDIR  (default CNF_GNUTAR_LIST_DIR)
+ * DIRECTORY       (no default, if set, the backup will be from that directory
+ *                 instead of from the --device)
+ * ONE-FILE-SYSTEM (default YES)
+ * SPARSE          (default YES)
+ * ATIME-PRESERVE  (default YES)
+ * CHECK-DEVICE    (default YES)
+ * INCLUDE-FILE
+ * INCLUDE-LIST
+ * INCLUDE-OPTIONAL
+ * EXCLUDE-FILE
+ * EXCLUDE-LIST
+ * EXCLUDE-OPTIONAL
+ * NORMAL
+ * IGNORE
+ * STRANGE
+ * EXIT-HANDLING   (1=GOOD 2=BAD)
+ * TAR-BLOCKSIZE   (default does not add --blocking-factor option,
+ *                  using tar's default)
+ */
+
+#include "amanda.h"
+#include "pipespawn.h"
+#include "amfeatures.h"
+#include "clock.h"
+#include "util.h"
+#include "getfsent.h"
+#include "version.h"
+#include "client_util.h"
+#include "conffile.h"
+#include "amandad.h"
+#include "getopt.h"
+#include "sendbackup.h"
+
+int debug_application = 1;
+#define application_debug(i, ...) do { \
+       if ((i) <= debug_application) { \
+           dbprintf(__VA_ARGS__);      \
+       }                               \
+} while (0)
+
+static amregex_t init_re_table[] = {
+  /* tar prints the size in bytes */
+  AM_SIZE_RE("^ *Total bytes written: [0-9][0-9]*", 1, 1),
+  AM_NORMAL_RE("^could not open conf file"),
+  AM_NORMAL_RE("^Elapsed time:"),
+  AM_NORMAL_RE("^Throughput"),
+  AM_IGNORE_RE(": Directory is new$"),
+  AM_IGNORE_RE(": Directory has been renamed"),
+
+  /* GNU tar 1.13.17 will print this warning when (not) backing up a
+     Unix named socket.  */
+  AM_NORMAL_RE(": socket ignored$"),
+
+  /* GNUTAR produces a few error messages when files are modified or
+     removed while it is running.  They may cause data to be lost, but
+     then they may not.  We shouldn't consider them NORMAL until
+     further investigation.  */
+  AM_NORMAL_RE(": File .* shrunk by [0-9][0-9]* bytes, padding with zeros"),
+  AM_NORMAL_RE(": Cannot add file .*: No such file or directory$"),
+  AM_NORMAL_RE(": Error exit delayed from previous errors"),
+  
+  /* catch-all: DMP_STRANGE is returned for all other lines */
+  AM_STRANGE_RE(NULL)
+};
+static amregex_t *re_table;
+
+/* local functions */
+int main(int argc, char **argv);
+
+typedef struct application_argument_s {
+    char      *config;
+    char      *host;
+    int        message;
+    int        collection;
+    int        calcsize;
+    char      *tar_blocksize;
+    GSList    *level;
+    dle_t      dle;
+    int        argc;
+    char     **argv;
+} application_argument_t;
+
+enum { CMD_ESTIMATE, CMD_BACKUP };
+
+static void amgtar_support(application_argument_t *argument);
+static void amgtar_selfcheck(application_argument_t *argument);
+static void amgtar_estimate(application_argument_t *argument);
+static void amgtar_backup(application_argument_t *argument);
+static void amgtar_restore(application_argument_t *argument);
+static void amgtar_validate(application_argument_t *argument);
+static void amgtar_build_exinclude(dle_t *dle, int verbose,
+                                  int *nb_exclude, char **file_exclude,
+                                  int *nb_include, char **file_include);
+static char *amgtar_get_incrname(application_argument_t *argument, int level);
+static char **amgtar_build_argv(application_argument_t *argument,
+                               char *incrname, int command);
+static amregex_t *build_re_table(amregex_t *orig_re_table,
+                                GSList *normal_message,
+                                GSList *ignore_message,
+                                GSList *strange_message);
+static void add_type_table(dmpline_t typ,
+                          amregex_t **re_table, amregex_t *orig_re_table,
+                          GSList *normal_message, GSList *ignore_message,
+                          GSList *strange_message);
+static void add_list_table(dmpline_t typ, amregex_t **re_table,
+                         GSList *message);
+static char *gnutar_path;
+static char *gnutar_listdir;
+static char *gnutar_directory;
+static int gnutar_onefilesystem;
+static int gnutar_atimepreserve;
+static int gnutar_checkdevice;
+static int gnutar_sparse;
+static GSList *normal_message = NULL;
+static GSList *ignore_message = NULL;
+static GSList *strange_message = NULL;
+static char   *exit_handling;
+static int    exit_value[256];
+
+static struct option long_options[] = {
+    {"config"          , 1, NULL,  1},
+    {"host"            , 1, NULL,  2},
+    {"disk"            , 1, NULL,  3},
+    {"device"          , 1, NULL,  4},
+    {"level"           , 1, NULL,  5},
+    {"index"           , 1, NULL,  6},
+    {"message"         , 1, NULL,  7},
+    {"collection"      , 0, NULL,  8},
+    {"record"          , 0, NULL,  9},
+    {"gnutar-path"     , 1, NULL, 10},
+    {"gnutar-listdir"  , 1, NULL, 11},
+    {"one-file-system" , 1, NULL, 12},
+    {"sparse"          , 1, NULL, 13},
+    {"atime-preserve"  , 1, NULL, 14},
+    {"check-device"    , 1, NULL, 15},
+    {"include-file"    , 1, NULL, 16},
+    {"include-list"    , 1, NULL, 17},
+    {"include-optional", 1, NULL, 18},
+    {"exclude-file"    , 1, NULL, 19},
+    {"exclude-list"    , 1, NULL, 20},
+    {"exclude-optional", 1, NULL, 21},
+    {"directory"       , 1, NULL, 22},
+    {"normal"          , 1, NULL, 23},
+    {"ignore"          , 1, NULL, 24},
+    {"strange"         , 1, NULL, 25},
+    {"exit-handling"   , 1, NULL, 26},
+    {"calcsize"        , 0, NULL, 27},
+    {"tar-blocksize"   , 1, NULL, 28},
+    {NULL, 0, NULL, 0}
+};
+
+
+void
+add_type_table(
+    dmpline_t   typ,
+    amregex_t **re_table,
+    amregex_t  *orig_re_table,
+    GSList     *normal_message,
+    GSList     *ignore_message,
+    GSList     *strange_message)
+{
+    amregex_t *rp;
+
+    for(rp = orig_re_table; rp->regex != NULL; rp++) {
+       if (rp->typ == typ) {
+           int     found = 0;
+           GSList *mes;
+
+           for (mes = normal_message; mes != NULL; mes = mes->next) {
+               if (strcmp(rp->regex, (char *)mes->data) == 0)
+                   found = 1;
+           }
+           for (mes = ignore_message; mes != NULL; mes = mes->next) {
+               if (strcmp(rp->regex, (char *)mes->data) == 0)
+                   found = 1;
+           }
+           for (mes = strange_message; mes != NULL; mes = mes->next) {
+               if (strcmp(rp->regex, (char *)mes->data) == 0)
+                   found = 1;
+           }
+           if (found == 0) {
+               (*re_table)->regex   = rp->regex;
+               (*re_table)->srcline = rp->srcline;
+               (*re_table)->scale   = rp->scale;
+               (*re_table)->field   = rp->field;
+               (*re_table)->typ     = rp->typ;
+               (*re_table)++;
+           }
+       }
+    }
+}
+
+void
+add_list_table(
+    dmpline_t   typ,
+    amregex_t **re_table,
+    GSList     *message)
+{
+    GSList *mes;
+
+    for (mes = message; mes != NULL; mes = mes->next) {
+       (*re_table)->regex = (char *)mes->data;
+       (*re_table)->srcline = 0;
+       (*re_table)->scale   = 0;
+       (*re_table)->field   = 0;
+       (*re_table)->typ     = typ;
+       (*re_table)++;
+    }
+}
+
+amregex_t *
+build_re_table(
+    amregex_t *orig_re_table,
+    GSList    *normal_message,
+    GSList    *ignore_message,
+    GSList    *strange_message)
+{
+    int        nb = 0;
+    amregex_t *rp;
+    amregex_t *re_table, *new_re_table;
+
+    for(rp = orig_re_table; rp->regex != NULL; rp++) {
+       nb++;
+    }
+    nb += g_slist_length(normal_message);
+    nb += g_slist_length(ignore_message);
+    nb += g_slist_length(strange_message);
+    nb ++;
+
+    re_table =  new_re_table = malloc(nb * sizeof(amregex_t));
+    
+    /* add SIZE from orig_re_table */
+    add_type_table(DMP_SIZE, &re_table, orig_re_table,
+                  normal_message, ignore_message, strange_message);
+
+    /* add ignore_message */
+    add_list_table(DMP_IGNORE, &re_table, ignore_message);
+
+    /* add IGNORE from orig_re_table */
+    add_type_table(DMP_IGNORE, &re_table, orig_re_table,
+                  normal_message, ignore_message, strange_message);
+
+    /* add normal_message */
+    add_list_table(DMP_NORMAL, &re_table, normal_message);
+
+    /* add NORMAL from orig_re_table */
+    add_type_table(DMP_NORMAL, &re_table, orig_re_table,
+                  normal_message, ignore_message, strange_message);
+
+    /* add strange_message */
+    add_list_table(DMP_STRANGE, &re_table, strange_message);
+
+    /* add STRANGE from orig_re_table */
+    add_type_table(DMP_STRANGE, &re_table, orig_re_table,
+                  normal_message, ignore_message, strange_message);
+
+    /* Add DMP_STRANGE with NULL regex,       */
+    /* it is not copied by previous statement */
+    re_table->regex = NULL;
+    re_table->srcline = 0;
+    re_table->scale = 0;
+    re_table->field = 0;
+    re_table->typ = DMP_STRANGE;
+
+    return new_re_table;
+}
+
+int
+main(
+    int                argc,
+    char **    argv)
+{
+    int c;
+    char *command;
+    application_argument_t argument;
+    int i;
+
+#ifdef GNUTAR
+    gnutar_path = GNUTAR;
+#else
+    gnutar_path = NULL;
+#endif
+    gnutar_directory = NULL;
+    gnutar_onefilesystem = 1;
+    gnutar_atimepreserve = 1;
+    gnutar_checkdevice = 1;
+    gnutar_sparse = 1;
+    exit_handling = NULL;
+
+    /* 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"); 
+
+    /* drop root privileges */
+    if (!set_root_privs(0)) {
+       error(_("amgtar must be run setuid root"));
+    }
+
+    safe_fd(3, 2);
+
+    set_pname("amgtar");
+
+    /* Don't die when child closes pipe */
+    signal(SIGPIPE, SIG_IGN);
+
+#if defined(USE_DBMALLOC)
+    malloc_size_1 = malloc_inuse(&malloc_hist_1);
+#endif
+
+    erroutput_type = (ERR_INTERACTIVE|ERR_SYSLOG);
+    dbopen(DBG_SUBDIR_CLIENT);
+    startclock();
+    dbprintf(_("version %s\n"), version());
+
+    config_init(CONFIG_INIT_CLIENT, NULL);
+
+    //check_running_as(RUNNING_AS_DUMPUSER_PREFERRED);
+    //root for amrecover
+    //RUNNING_AS_CLIENT_LOGIN from selfcheck, sendsize, sendbackup
+
+    /* parse argument */
+    command = argv[1];
+
+    argument.config     = NULL;
+    argument.host       = NULL;
+    argument.message    = 0;
+    argument.collection = 0;
+    argument.calcsize   = 0;
+    argument.tar_blocksize = NULL;
+    argument.level      = NULL;
+    init_dle(&argument.dle);
+
+    while (1) {
+       int option_index = 0;
+       c = getopt_long (argc, argv, "", long_options, &option_index);
+       if (c == -1) {
+           break;
+       }
+       switch (c) {
+       case 1: argument.config = stralloc(optarg);
+               break;
+       case 2: argument.host = stralloc(optarg);
+               break;
+       case 3: argument.dle.disk = stralloc(optarg);
+               break;
+       case 4: argument.dle.device = stralloc(optarg);
+               break;
+       case 5: argument.level = g_slist_append(argument.level,
+                                               GINT_TO_POINTER(atoi(optarg)));
+               break;
+       case 6: argument.dle.create_index = 1;
+               break;
+       case 7: argument.message = 1;
+               break;
+       case 8: argument.collection = 1;
+               break;
+       case 9: argument.dle.record = 1;
+               break;
+       case 10: gnutar_path = stralloc(optarg);
+                break;
+       case 11: gnutar_listdir = stralloc(optarg);
+                break;
+       case 12: if (optarg && strcasecmp(optarg, "YES") != 0)
+                    gnutar_onefilesystem = 0;
+                break;
+       case 13: if (optarg && strcasecmp(optarg, "YES") != 0)
+                    gnutar_sparse = 0;
+                break;
+       case 14: if (optarg && strcasecmp(optarg, "YES") != 0)
+                    gnutar_atimepreserve = 0;
+                break;
+       case 15: if (optarg && strcasecmp(optarg, "YES") != 0)
+                    gnutar_checkdevice = 0;
+                break;
+       case 16: if (optarg)
+                    argument.dle.include_file =
+                        append_sl(argument.dle.include_file, optarg);
+                break;
+       case 17: if (optarg)
+                    argument.dle.include_list =
+                        append_sl(argument.dle.include_list, optarg);
+                break;
+       case 18: argument.dle.include_optional = 1;
+                break;
+       case 19: if (optarg)
+                    argument.dle.exclude_file =
+                        append_sl(argument.dle.exclude_file, optarg);
+                break;
+       case 20: if (optarg)
+                    argument.dle.exclude_list =
+                        append_sl(argument.dle.exclude_list, optarg);
+                break;
+       case 21: argument.dle.exclude_optional = 1;
+                break;
+       case 22: gnutar_directory = stralloc(optarg);
+                break;
+       case 23: if (optarg)
+                    normal_message = 
+                        g_slist_append(normal_message, optarg);
+                break;
+       case 24: if (optarg)
+                    ignore_message = 
+                        g_slist_append(ignore_message, optarg);
+                break;
+       case 25: if (optarg)
+                    strange_message = 
+                        g_slist_append(strange_message, optarg);
+                break;
+       case 26: if (optarg)
+                    exit_handling = stralloc(optarg);
+                break;
+       case 27: argument.calcsize = 1;
+                break;
+       case 28: argument.tar_blocksize = stralloc(optarg);
+       case ':':
+       case '?':
+               break;
+       }
+    }
+
+    argument.argc = argc - optind;
+    argument.argv = argv + optind;
+
+    if (argument.config) {
+       config_init(CONFIG_INIT_CLIENT | CONFIG_INIT_EXPLICIT_NAME | CONFIG_INIT_OVERLAY,
+                   argument.config);
+       dbrename(get_config_name(), DBG_SUBDIR_CLIENT);
+    }
+
+    if (config_errors(NULL) >= CFGERR_ERRORS) {
+       g_critical(_("errors processing config file"));
+    }
+
+    re_table = build_re_table(init_re_table, normal_message, ignore_message,
+                             strange_message);
+
+    for(i=0;i<256;i++)
+       exit_value[i] = 1; /* BAD  */
+    exit_value[0] = 0;     /* GOOD */
+    exit_value[1] = 0;     /* GOOD */
+    if (exit_handling) {
+       char *s = exit_handling;
+       while (s) {
+           char *r = index(s, '=');
+           if (r) {
+               int j = atoi(s);
+               if (j >= 0 && j < 256) {
+                   r++;
+                   if (strncasecmp(r, "GOOD", 4) == 0) {
+                       exit_value[j] = 0;
+                   }
+                   s = index(s, ' ');
+               }
+           }
+       }
+    }
+
+    gnutar_listdir = getconf_str(CNF_GNUTAR_LIST_DIR);
+    if (strlen(gnutar_listdir) == 0)
+       gnutar_listdir = NULL;
+
+    if (gnutar_path) {
+       dbprintf("GNUTAR-PATH %s\n", gnutar_path);
+    } else {
+       dbprintf("GNUTAR-PATH is not set\n");
+    }
+    if (gnutar_listdir) {
+           dbprintf("GNUTAR-LISTDIR %s\n", gnutar_listdir);
+    } else {
+       dbprintf("GNUTAR-LISTDIR is not set\n");
+    }
+    if (gnutar_directory) {
+       dbprintf("DIRECTORY %s\n", gnutar_directory);
+    }
+    dbprintf("ONE-FILE-SYSTEM %s\n", gnutar_onefilesystem? "yes":"no");
+    dbprintf("SPARSE %s\n", gnutar_sparse? "yes":"no");
+    dbprintf("ATIME-PRESERVE %s\n", gnutar_atimepreserve? "yes":"no");
+    dbprintf("CHECK-DEVICE %s\n", gnutar_checkdevice? "yes":"no");
+    {
+       amregex_t *rp;
+       for (rp = re_table; rp->regex != NULL; rp++) {
+           switch (rp->typ) {
+               case DMP_NORMAL : dbprintf("NORMAL %s\n", rp->regex); break;
+               case DMP_IGNORE : dbprintf("IGNORE %s\n", rp->regex); break;
+               case DMP_STRANGE: dbprintf("STRANGE %s\n", rp->regex); break;
+               case DMP_SIZE   : dbprintf("SIZE %s\n", rp->regex); break;
+               case DMP_ERROR  : dbprintf("ERROR %s\n", rp->regex); break;
+           }
+       }
+    }
+
+    if (strcmp(command, "support") == 0) {
+       amgtar_support(&argument);
+    } else if (strcmp(command, "selfcheck") == 0) {
+       amgtar_selfcheck(&argument);
+    } else if (strcmp(command, "estimate") == 0) {
+       amgtar_estimate(&argument);
+    } else if (strcmp(command, "backup") == 0) {
+       amgtar_backup(&argument);
+    } else if (strcmp(command, "restore") == 0) {
+       amgtar_restore(&argument);
+    } else if (strcmp(command, "validate") == 0) {
+       amgtar_validate(&argument);
+    } else {
+       dbprintf("Unknown command `%s'.\n", command);
+       fprintf(stderr, "Unknown command `%s'.\n", command);
+       exit (1);
+    }
+    return 0;
+}
+
+static void
+amgtar_support(
+    application_argument_t *argument)
+{
+    (void)argument;
+    fprintf(stdout, "CONFIG YES\n");
+    fprintf(stdout, "HOST YES\n");
+    fprintf(stdout, "DISK YES\n");
+    fprintf(stdout, "MAX-LEVEL 9\n");
+    fprintf(stdout, "INDEX-LINE YES\n");
+    fprintf(stdout, "INDEX-XML NO\n");
+    fprintf(stdout, "MESSAGE-LINE YES\n");
+    fprintf(stdout, "MESSAGE-XML NO\n");
+    fprintf(stdout, "RECORD YES\n");
+    fprintf(stdout, "INCLUDE-FILE YES\n");
+    fprintf(stdout, "INCLUDE-LIST YES\n");
+    fprintf(stdout, "INCLUDE-OPTIONAL YES\n");
+    fprintf(stdout, "EXCLUDE-FILE YES\n");
+    fprintf(stdout, "EXCLUDE-LIST YES\n");
+    fprintf(stdout, "EXCLUDE-OPTIONAL YES\n");
+    fprintf(stdout, "COLLECTION NO\n");
+    fprintf(stdout, "MULTI-ESTIMATE YES\n");
+    fprintf(stdout, "CALCSIZE YES\n");
+}
+
+static void
+amgtar_selfcheck(
+    application_argument_t *argument)
+{
+    amgtar_build_exinclude(&argument->dle, 1, NULL, NULL, NULL, NULL);
+
+    if (gnutar_path) {
+       check_file(gnutar_path, X_OK);
+    } else {
+       printf(_("ERROR [GNUTAR program not available]\n"));
+    }
+
+    set_root_privs(1);
+    if (gnutar_listdir && strlen(gnutar_listdir) == 0)
+       gnutar_listdir = NULL;
+    if (gnutar_listdir) {
+       check_dir(gnutar_listdir, R_OK|W_OK);
+    } else {
+       printf(_("ERROR [No GNUTAR-LISTDIR]\n"));
+    }
+
+    fprintf(stdout, "OK %s\n", argument->dle.disk);
+    if (gnutar_directory) {
+       check_dir(gnutar_directory, R_OK);
+    } else {
+       check_dir(argument->dle.device, R_OK);
+    }
+    set_root_privs(0);
+}
+
+static void
+amgtar_estimate(
+    application_argument_t *argument)
+{
+    char  *incrname = NULL;
+    char **my_argv = NULL;
+    char  *cmd = NULL;
+    int    nullfd = -1;
+    int    pipefd = -1;
+    FILE  *dumpout = NULL;
+    off_t  size = -1;
+    char   line[32768];
+    char  *errmsg = NULL;
+    char  *qerrmsg = NULL;
+    char  *qdisk;
+    amwait_t wait_status;
+    int tarpid;
+    amregex_t *rp;
+    times_t start_time;
+    int     level;
+    GSList *levels;
+
+    qdisk = quote_string(argument->dle.disk);
+
+    if (argument->calcsize) {
+       char *dirname;
+       char *file_exclude;
+       char *file_include;
+       int   nb_exclude;
+       int   nb_include;
+
+       if (gnutar_directory) {
+           dirname = gnutar_directory;
+       } else {
+           dirname = amname_to_dirname(argument->dle.device);
+       }
+       amgtar_build_exinclude(&argument->dle, 1,
+                              &nb_exclude, &file_exclude,
+                              &nb_include, &file_include);
+
+       run_calcsize(argument->config, "GNUTAR", argument->dle.disk, dirname,
+                    argument->level, file_exclude, file_include);
+       return;
+    }
+
+    if (!gnutar_path) {
+       errmsg = vstrallocf(_("GNUTAR-PATH not defined"));
+       goto common_error;
+    }
+
+    if (!gnutar_listdir) {
+       errmsg = vstrallocf(_("GNUTAR-LISTDIR not defined"));
+       goto common_error;
+    }
+
+    for (levels = argument->level; levels != NULL; levels = levels->next) {
+       level = GPOINTER_TO_INT(levels->data);
+       incrname = amgtar_get_incrname(argument, level);
+       cmd = stralloc(gnutar_path);
+       my_argv = amgtar_build_argv(argument, incrname, CMD_ESTIMATE);
+
+       start_time = curclock();
+
+       if ((nullfd = open("/dev/null", O_RDWR)) == -1) {
+           errmsg = vstrallocf(_("Cannot access /dev/null : %s"),
+                               strerror(errno));
+           goto common_exit;
+       }
+
+       tarpid = pipespawnv(cmd, STDERR_PIPE, 1,
+                           &nullfd, &nullfd, &pipefd, my_argv);
+
+       dumpout = fdopen(pipefd,"r");
+       if (!dumpout) {
+           error(_("Can't fdopen: %s"), strerror(errno));
+           /*NOTREACHED*/
+       }
+
+       size = (off_t)-1;
+       while (size < 0 && (fgets(line, sizeof(line), dumpout) != NULL)) {
+           if (line[strlen(line)-1] == '\n') /* remove trailling \n */
+               line[strlen(line)-1] = '\0';
+           if (line[0] == '\0')
+               continue;
+           dbprintf("%s\n", line);
+           /* check for size match */
+           /*@ignore@*/
+           for(rp = re_table; rp->regex != NULL; rp++) {
+               if(match(rp->regex, line)) {
+                   if (rp->typ == DMP_SIZE) {
+                       size = ((the_num(line, rp->field)*rp->scale+1023.0)/1024.0);
+                       if(size < 0.0)
+                           size = 1.0;             /* found on NeXT -- sigh */
+                   }
+                   break;
+               }
+           }
+           /*@end@*/
+       }
+
+       while (fgets(line, sizeof(line), dumpout) != NULL) {
+           dbprintf("%s", line);
+       }
+
+       dbprintf(".....\n");
+       dbprintf(_("estimate time for %s level %d: %s\n"),
+                qdisk,
+                level,
+                walltime_str(timessub(curclock(), start_time)));
+       if(size == (off_t)-1) {
+           errmsg = vstrallocf(_("no size line match in %s output"),
+                               my_argv[0]);
+           dbprintf(_("%s for %s\n"), errmsg, qdisk);
+           dbprintf(".....\n");
+       } else if(size == (off_t)0 && argument->level == 0) {
+           dbprintf(_("possible %s problem -- is \"%s\" really empty?\n"),
+                     my_argv[0], argument->dle.disk);
+           dbprintf(".....\n");
+       }
+       dbprintf(_("estimate size for %s level %d: %lld KB\n"),
+                qdisk,
+                level,
+                (long long)size);
+
+       kill(-tarpid, SIGTERM);
+
+       dbprintf(_("waiting for %s \"%s\" child\n"), my_argv[0], qdisk);
+       waitpid(tarpid, &wait_status, 0);
+       if (WIFSIGNALED(wait_status)) {
+           errmsg = vstrallocf(_("%s terminated with signal %d: see %s"),
+                               cmd, WTERMSIG(wait_status), dbfn());
+       } else if (WIFEXITED(wait_status)) {
+           if (exit_value[WEXITSTATUS(wait_status)] == 1) {
+               errmsg = vstrallocf(_("%s exited with status %d: see %s"),
+                                   cmd, WEXITSTATUS(wait_status), dbfn());
+           } else {
+               /* Normal exit */
+           }
+       } else {
+           errmsg = vstrallocf(_("%s got bad exit: see %s"),
+                               cmd, dbfn());
+       }
+       dbprintf(_("after %s %s wait\n"), my_argv[0], qdisk);
+
+common_exit:
+       if (errmsg) {
+           dbprintf("%s", errmsg);
+           fprintf(stdout, "ERROR %s\n", errmsg);
+       }
+
+       if (incrname) {
+           unlink(incrname);
+       }
+       amfree(my_argv);
+       amfree(cmd);
+
+       aclose(nullfd);
+       afclose(dumpout);
+
+       fprintf(stdout, "%d %lld 1\n", level, (long long)size);
+    }
+    amfree(qdisk);
+    return;
+
+common_error:
+    qerrmsg = quote_string(errmsg);
+    amfree(qdisk);
+    dbprintf("%s", errmsg);
+    fprintf(stdout, "ERROR %s\n", qerrmsg);
+    amfree(errmsg);
+    amfree(qerrmsg);
+    return;
+}
+
+static void
+amgtar_backup(
+    application_argument_t *argument)
+{
+    int dumpin;
+    char *cmd = NULL;
+    char *qdisk;
+    char *incrname;
+    char line[32768];
+    amregex_t *rp;
+    off_t dump_size = -1;
+    char *type;
+    char startchr;
+
+    int dataf = 1;
+    int mesgf = 3;
+    int indexf = 4;
+    int outf;
+    FILE *mesgstream;
+    FILE *indexstream = NULL;
+    FILE *outstream;
+    char *errmsg = NULL;
+    amwait_t wait_status;
+
+    char **my_argv;
+    int tarpid;
+
+    if (!gnutar_path) {
+       error(_("GNUTAR-PATH not defined"));
+    }
+    if (!gnutar_listdir) {
+       error(_("GNUTAR-LISTDIR not defined"));
+    }
+
+    qdisk = quote_string(argument->dle.disk);
+
+    incrname = amgtar_get_incrname(argument,
+                                  GPOINTER_TO_INT(argument->level->data));
+    cmd = stralloc(gnutar_path);
+    my_argv = amgtar_build_argv(argument, incrname, CMD_BACKUP);
+
+    tarpid = pipespawnv(cmd, STDIN_PIPE|STDERR_PIPE, 1,
+                       &dumpin, &dataf, &outf, my_argv);
+    /* close the write ends of the pipes */
+
+    aclose(dumpin);
+    aclose(dataf);
+    if (argument->dle.create_index) {
+       indexstream = fdopen(indexf, "w");
+       if (!indexstream) {
+           error(_("error indexstream(%d): %s\n"), indexf, strerror(errno));
+       }
+    }
+    mesgstream = fdopen(mesgf, "w");
+    if (!mesgstream) {
+       error(_("error mesgstream(%d): %s\n"), mesgf, strerror(errno));
+    }
+    outstream = fdopen(outf, "r");
+    if (!outstream) {
+       error(_("error outstream(%d): %s\n"), outf, strerror(errno));
+    }
+
+    while (fgets(line, sizeof(line), outstream) != NULL) {
+       if (line[strlen(line)-1] == '\n') /* remove trailling \n */
+           line[strlen(line)-1] = '\0';
+       if (*line == '.' && *(line+1) == '/') { /* filename */
+           if (argument->dle.create_index) {
+               fprintf(indexstream, "%s\n", &line[1]); /* remove . */
+           }
+       } else { /* message */
+           for(rp = re_table; rp->regex != NULL; rp++) {
+               if(match(rp->regex, line)) {
+                   break;
+               }
+           }
+           if(rp->typ == DMP_SIZE) {
+               dump_size = (long)((the_num(line, rp->field)* rp->scale+1023.0)/1024.0);
+           }
+           switch(rp->typ) {
+           case DMP_NORMAL:
+               type = "normal";
+               startchr = '|';
+               break;
+           case DMP_IGNORE:
+               continue;
+           case DMP_STRANGE:
+               type = "strange";
+               startchr = '?';
+               break;
+           case DMP_SIZE:
+               type = "size";
+               startchr = '|';
+               break;
+           case DMP_ERROR:
+               type = "error";
+               startchr = '?';
+               break;
+           default:
+               type = "unknown";
+               startchr = '!';
+               break;
+           }
+           dbprintf("%3d: %7s(%c): %s\n", rp->srcline, type, startchr, line);
+           fprintf(mesgstream,"%c %s\n", startchr, line);
+        }
+    }
+
+    waitpid(tarpid, &wait_status, 0);
+    if (WIFSIGNALED(wait_status)) {
+       errmsg = vstrallocf(_("%s terminated with signal %d: see %s"),
+                           cmd, WTERMSIG(wait_status), dbfn());
+    } else if (WIFEXITED(wait_status)) {
+       if (exit_value[WEXITSTATUS(wait_status)] == 1) {
+           errmsg = vstrallocf(_("%s exited with status %d: see %s"),
+                               cmd, WEXITSTATUS(wait_status), dbfn());
+       } else {
+           /* Normal exit */
+       }
+    } else {
+       errmsg = vstrallocf(_("%s got bad exit: see %s"),
+                           cmd, dbfn());
+    }
+    dbprintf(_("after %s %s wait\n"), my_argv[0], qdisk);
+    dbprintf(_("amgtar: %s: pid %ld\n"), cmd, (long)tarpid);
+    if (errmsg) {
+       dbprintf("%s", errmsg);
+       g_fprintf(mesgstream, "sendbackup: error [%s]\n", errmsg);
+    }
+
+    if (incrname && strlen(incrname) > 4) {
+       char *nodotnew;
+       nodotnew = stralloc(incrname);
+       nodotnew[strlen(nodotnew)-4] = '\0';
+       if (rename(incrname, nodotnew)) {
+           dbprintf(_("%s: warning [renaming %s to %s: %s]\n"),
+                    get_pname(), incrname, nodotnew, strerror(errno));
+           g_fprintf(mesgstream, _("? warning [renaming %s to %s: %s]\n"),
+                     incrname, nodotnew, strerror(errno));
+       }
+       amfree(nodotnew);
+    }
+
+    dbprintf("sendbackup: size %lld\n", (long long)dump_size);
+    fprintf(mesgstream, "sendbackup: size %lld\n", (long long)dump_size);
+    dbprintf("sendbackup: end\n");
+    fprintf(mesgstream, "sendbackup: end\n");
+
+    if (argument->dle.create_index)
+       fclose(indexstream);
+
+    fclose(mesgstream);
+
+    amfree(incrname);
+    amfree(qdisk);
+    amfree(cmd);
+}
+
+static void
+amgtar_restore(
+    application_argument_t *argument)
+{
+    char  *cmd;
+    char **my_argv;
+    char **env;
+    int    i, j;
+    char  *e;
+
+    if (!gnutar_path) {
+       error(_("GNUTAR-PATH not defined"));
+    }
+
+    cmd = stralloc(gnutar_path);
+    my_argv = alloc(SIZEOF(char *) * (6 + argument->argc));
+    i = 0;
+    my_argv[i++] = stralloc(gnutar_path);
+    my_argv[i++] = stralloc("--numeric-owner");
+    my_argv[i++] = stralloc("-xpGvf");
+    my_argv[i++] = stralloc("-");
+
+    for (j=1; j< argument->argc; j++) {
+       my_argv[i++] = stralloc(argument->argv[j]);
+    }
+    my_argv[i++] = NULL;
+
+    env = safe_env();
+    become_root();
+    execve(cmd, my_argv, env);
+    e = strerror(errno);
+    error(_("error [exec %s: %s]"), cmd, e);
+}
+
+static void
+amgtar_validate(
+    application_argument_t *argument G_GNUC_UNUSED)
+{
+    char  *cmd;
+    char **my_argv;
+    char **env;
+    int    i;
+    char  *e;
+
+    if (!gnutar_path) {
+       error(_("GNUTAR-PATH not defined"));
+    }
+
+    cmd = stralloc(gnutar_path);
+    my_argv = alloc(SIZEOF(char *) * 4);
+    i = 0;
+    my_argv[i++] = stralloc(gnutar_path);
+    my_argv[i++] = stralloc("-tf");
+    my_argv[i++] = stralloc("-");
+    my_argv[i++] = NULL;
+
+    env = safe_env();
+    execve(cmd, my_argv, env);
+    e = strerror(errno);
+    error(_("error [exec %s: %s]"), cmd, e);
+}
+
+static void
+amgtar_build_exinclude(
+    dle_t  *dle,
+    int     verbose,
+    int    *nb_exclude,
+    char  **file_exclude,
+    int    *nb_include,
+    char  **file_include)
+{
+    int n_exclude = 0;
+    int n_include = 0;
+    char *exclude = NULL;
+    char *include = NULL;
+
+    if (dle->exclude_file) n_exclude += dle->exclude_file->nb_element;
+    if (dle->exclude_list) n_exclude += dle->exclude_list->nb_element;
+    if (dle->include_file) n_include += dle->include_file->nb_element;
+    if (dle->include_list) n_include += dle->include_list->nb_element;
+
+    if (n_exclude > 0) exclude = build_exclude(dle, verbose);
+    if (n_include > 0) include = build_include(dle, verbose);
+
+    if (nb_exclude)
+       *nb_exclude = n_exclude;
+    if (file_exclude)
+       *file_exclude = exclude;
+    else
+       amfree(exclude);
+
+    if (nb_include)
+       *nb_include = n_include;
+    if (file_include)
+       *file_include = include;
+    else
+       amfree(include);
+}
+
+static char *
+amgtar_get_incrname(
+    application_argument_t *argument,
+    int                     level)
+{
+    char *basename = NULL;
+    char *incrname = NULL;
+    int   infd, outfd;
+    ssize_t   nb;
+    char *inputname = NULL;
+    char *errmsg = NULL;
+    char *buf;
+
+    if (gnutar_listdir) {
+       char number[NUM_STR_SIZE];
+       int baselevel;
+       char *sdisk = sanitise_filename(argument->dle.disk);
+
+       basename = vstralloc(gnutar_listdir,
+                            "/",
+                            argument->host,
+                            sdisk,
+                            NULL);
+       amfree(sdisk);
+
+       snprintf(number, SIZEOF(number), "%d", level);
+       incrname = vstralloc(basename, "_", number, ".new", NULL);
+       unlink(incrname);
+
+       /*
+        * Open the listed incremental file from the previous level.  Search
+        * backward until one is found.  If none are found (which will also
+        * be true for a level 0), arrange to read from /dev/null.
+        */
+       baselevel = level;
+       infd = -1;
+       while (infd == -1) {
+           if (--baselevel >= 0) {
+               snprintf(number, SIZEOF(number), "%d", baselevel);
+               inputname = newvstralloc(inputname,
+                                        basename, "_", number, NULL);
+           } else {
+               inputname = newstralloc(inputname, "/dev/null");
+           }
+           if ((infd = open(inputname, O_RDONLY)) == -1) {
+
+               errmsg = vstrallocf(_("amgtar: error opening %s: %s"),
+                                    inputname, strerror(errno));
+               dbprintf("%s\n", errmsg);
+               if (baselevel < 0) {
+                   return NULL;
+               }
+               amfree(errmsg);
+           }
+       }
+
+       /*
+        * Copy the previous listed incremental file to the new one.
+        */
+       if ((outfd = open(incrname, O_WRONLY|O_CREAT, 0600)) == -1) {
+           errmsg = vstrallocf(_("opening %s: %s"),
+                                incrname, strerror(errno));
+           dbprintf("%s\n", errmsg);
+           return NULL;
+       }
+
+       while ((nb = read(infd, &buf, SIZEOF(buf))) > 0) {
+           if (full_write(outfd, &buf, (size_t)nb) < (size_t)nb) {
+               errmsg = vstrallocf(_("writing to %s: %s"),
+                                    incrname, strerror(errno));
+               dbprintf("%s\n", errmsg);
+               return NULL;
+           }
+       }
+
+       if (nb < 0) {
+           errmsg = vstrallocf(_("reading from %s: %s"),
+                                inputname, strerror(errno));
+           dbprintf("%s\n", errmsg);
+           return NULL;
+       }
+
+       if (close(infd) != 0) {
+           errmsg = vstrallocf(_("closing %s: %s"),
+                                inputname, strerror(errno));
+           dbprintf("%s\n", errmsg);
+           return NULL;
+       }
+       if (close(outfd) != 0) {
+           errmsg = vstrallocf(_("closing %s: %s"),
+                                incrname, strerror(errno));
+           dbprintf("%s\n", errmsg);
+           return NULL;
+       }
+
+       amfree(inputname);
+       amfree(basename);
+    }
+    return incrname;
+}
+
+char **amgtar_build_argv(
+    application_argument_t *argument,
+    char *incrname,
+    int   command)
+{
+    int    i;
+    int    nb_exclude;
+    int    nb_include;
+    char  *file_exclude;
+    char  *file_include;
+    char  *dirname;
+    char   tmppath[PATH_MAX];
+    char **my_argv;
+
+    amgtar_build_exinclude(&argument->dle, 1,
+                          &nb_exclude, &file_exclude,
+                          &nb_include, &file_include);
+
+    if (gnutar_directory) {
+       dirname = gnutar_directory;
+    } else {
+       dirname = amname_to_dirname(argument->dle.device);
+    }
+
+    my_argv = alloc(SIZEOF(char *) * 23);
+    i = 0;
+
+    my_argv[i++] = gnutar_path;
+
+    my_argv[i++] = "--create";
+    if (command == CMD_BACKUP && argument->dle.create_index)
+       my_argv[i++] = "--verbose";
+    my_argv[i++] = "--file";
+    if (command == CMD_ESTIMATE) {
+       my_argv[i++] = "/dev/null";
+    } else {
+       my_argv[i++] = "-";
+    }
+    my_argv[i++] = "--directory";
+    canonicalize_pathname(dirname, tmppath);
+    my_argv[i++] = stralloc(tmppath);
+    if (gnutar_onefilesystem)
+       my_argv[i++] = "--one-file-system";
+    if (gnutar_atimepreserve)
+       my_argv[i++] = "--atime-preserve=system";
+    if (!gnutar_checkdevice)
+       my_argv[i++] = "--no-check-device";
+    my_argv[i++] = "--listed-incremental";
+    my_argv[i++] = incrname;
+    if (gnutar_sparse)
+       my_argv[i++] = "--sparse";
+    if (argument->tar_blocksize) {
+       my_argv[i++] = "--blocking-factor";
+       my_argv[i++] = argument->tar_blocksize;
+    }
+    my_argv[i++] = "--ignore-failed-read";
+    my_argv[i++] = "--totals";
+
+    if(file_exclude) {
+       my_argv[i++] = "--exclude-from";
+       my_argv[i++] = file_exclude;
+    }
+
+    if(file_include) {
+       my_argv[i++] = "--files-from";
+       my_argv[i++] = file_include;
+    }
+    else {
+       my_argv[i++] = ".";
+    }
+    my_argv[i++] = NULL;
+
+    return(my_argv);
+}
+