Imported Upstream version 3.3.2
[debian/amanda] / common-src / fileheader.c
index ede34793e14dd587bbf95051c86b35d857dc1266..381c40d4a74bb6ded14a49b34e665817f193d028 100644 (file)
  * file named AUTHORS, in the root directory of this distribution.
  */
 /*
- * $Id: fileheader.c,v 1.34 2006/03/09 16:51:41 martinea Exp $
+ * $Id: fileheader.c 6512 2007-05-24 17:00:24Z ian $
  */
 
 #include "amanda.h"
 #include "fileheader.h"
+#include "match.h"
+#include <glib.h>
+#include "util.h"
 
-static const char *filetype2str P((filetype_t));
-static filetype_t str2filetype P((const char *));
+static const char *    filetype2str(filetype_t);
+static filetype_t      str2filetype(const char *);
+static void            strange_header(dumpfile_t *, const char *,
+                               size_t, const char *, const char *);
+static char            *quote_heredoc(char *text, char *delimiter_prefix);
+static char            *parse_heredoc(char *line, char **saveptr);
 
 void
-fh_init(file)
-    dumpfile_t *file;
+fh_init(
+    dumpfile_t *file)
 {
-    memset(file, '\0', sizeof(*file));
-    file->blocksize = DISK_BLOCK_BYTES;
+    memset(file, '\0', SIZEOF(*file));
+    file->type = F_EMPTY;
+    file->blocksize = 0;
+}
+
+static void
+strange_header(
+    dumpfile_t *file,
+    const char *buffer,
+    size_t     buflen,
+    const char *expected,
+    const char *actual)
+{
+    if (actual == NULL)
+       actual = "<null>";
+    if (expected == NULL)
+       expected = "<null>";
+
+    g_debug("strange amanda header: \"%.*s\"", (int)buflen, buffer);
+    g_debug("Expected: \"%s\"  Actual: \"%s\"", expected, actual);
+
+    file->type = F_WEIRD;
+}
+
+/* chop whitespace off of a string, in place */
+static void
+chomp(char *str)
+{
+    char *s = str;
+
+    if (!str)
+       return;
+
+    /* trim leading space */
+    while (g_ascii_isspace(*s)) { s++; }
+    if (s != str)
+       memmove(str, s, strlen(s)+1);
+
+    /* trim trailing space */
+    if (*str) {
+       for (s = str+strlen(str)-1; s >= str; s--) {
+           if (!g_ascii_isspace(*s))
+               break;
+           *s = '\0';
+       }
+    }
 }
 
 void
-parse_file_header(buffer, file, buflen)
-    const char *buffer;
-    dumpfile_t *file;
-    size_t buflen;
+parse_file_header(
+    const char *buffer,
+    dumpfile_t *file,
+    size_t     buflen)
 {
-    char *buf, *line, *tok, *line1=NULL;
-    int lsize;
+    char *buf, *line, *tok, *line1;
+    size_t lsize;
+    char *uqname;
+    int in_quotes;
+    char *saveptr = NULL;
+
     /* put the buffer into a writable chunk of memory and nul-term it */
     buf = alloc(buflen + 1);
     memcpy(buf, buffer, buflen);
     buf[buflen] = '\0';
-
     fh_init(file); 
 
-    for(line=buf,lsize=0; *line != '\n' && lsize < buflen; line++) {lsize++;};
+    /* extract the first unquoted line */
+    in_quotes = 0;
+    for (line = buf, lsize = 0; lsize < buflen; line++) {
+       if ((*line == '\n') && !in_quotes)
+           break;
+
+       if (*line == '"') {
+           in_quotes = !in_quotes;
+       } else if ((*line == '\\') && (*(line + 1) == '"')) {
+           line++;
+           lsize++;
+       }
+       lsize++;
+    }
     *line = '\0';
-    line1 = alloc(lsize+1);
-    strncpy(line1,buf,lsize);
+    line1 = alloc(lsize + 1);
+    strncpy(line1, buf, lsize);
     line1[lsize] = '\0';
     *line = '\n';
 
-    tok = strtok(line1, " ");
-    if (tok == NULL)
-       goto weird_header;
+    tok = strtok_r(line1, " ", &saveptr);
+    if (tok == NULL) {
+        g_debug("Empty amanda header: buflen=%zu lsize=%zu buf='%s'", buflen, lsize, buf);
+       strange_header(file, buffer, buflen, _("<Non-empty line>"), tok);
+       goto out;
+    }
+
     if (strcmp(tok, "NETDUMP:") != 0 && strcmp(tok, "AMANDA:") != 0) {
        amfree(buf);
-       file->type = F_UNKNOWN;
+       file->type = F_WEIRD;
        amfree(line1);
        return;
     }
 
-    tok = strtok(NULL, " ");
-    if (tok == NULL)
-       goto weird_header;
+    tok = strtok_r(NULL, " ", &saveptr);
+    if (tok == NULL) {
+       strange_header(file, buffer, buflen, _("<file type>"), tok);
+       goto out;
+    }
     file->type = str2filetype(tok);
-
+    
     switch (file->type) {
     case F_TAPESTART:
-       tok = strtok(NULL, " ");
-       if (tok == NULL || strcmp(tok, "DATE") != 0)
-           goto weird_header;
-
-       tok = strtok(NULL, " ");
-       if (tok == NULL)
-           goto weird_header;
-       strncpy(file->datestamp, tok, sizeof(file->datestamp) - 1);
-
-       tok = strtok(NULL, " ");
-       if (tok == NULL || strcmp(tok, "TAPE") != 0)
-           goto weird_header;
-
-       tok = strtok(NULL, " ");
-       if (tok == NULL)
-           goto weird_header;
-       strncpy(file->name, tok, sizeof(file->name) - 1);
+       tok = strtok_r(NULL, " ", &saveptr);
+       if ((tok == NULL) || (strcmp(tok, "DATE") != 0)) {
+           strange_header(file, buffer, buflen, "DATE", tok);
+           goto out;
+       }
+
+       tok = strtok_r(NULL, " ", &saveptr);
+       if (tok == NULL) {
+           strange_header(file, buffer, buflen, _("<date stamp>"), tok);
+           goto out;
+       }
+       strncpy(file->datestamp, tok, SIZEOF(file->datestamp) - 1);
+
+       tok = strtok_r(NULL, " ", &saveptr);
+       if ((tok == NULL) || (strcmp(tok, "TAPE") != 0)) {
+           strange_header(file, buffer, buflen, "TAPE", tok);
+           goto out;
+       }
+
+       tok = strtok_r(NULL, " ", &saveptr);
+       if (tok == NULL) {
+           strange_header(file, buffer, buflen, _("<file type>"), tok);
+           goto out;
+       }
+       strncpy(file->name, tok, SIZEOF(file->name) - 1);
        break;
 
     case F_DUMPFILE:
     case F_CONT_DUMPFILE:
     case F_SPLIT_DUMPFILE:
-       tok = strtok(NULL, " ");
-       if (tok == NULL)
-           goto weird_header;
-       strncpy(file->datestamp, tok, sizeof(file->datestamp) - 1);
-
-       tok = strtok(NULL, " ");
-       if (tok == NULL)
-           goto weird_header;
-       strncpy(file->name, tok, sizeof(file->name) - 1);
-
-       tok = strtok(NULL, " ");
-       if (tok == NULL)
-           goto weird_header;
-       strncpy(file->disk, tok, sizeof(file->disk) - 1);
+       tok = strtok_r(NULL, " ", &saveptr);
+       if (tok == NULL) {
+           strange_header(file, buffer, buflen, _("<date stamp>"), tok);
+           goto out;
+       }
+       strncpy(file->datestamp, tok, SIZEOF(file->datestamp) - 1);
+
+       tok = strtok_r(NULL, " ", &saveptr);
+       if (tok == NULL) {
+           strange_header(file, buffer, buflen, _("<file name>"), tok);
+           goto out;
+       }
+       strncpy(file->name, tok, SIZEOF(file->name) - 1);
+
+       tok = strquotedstr(&saveptr);
+       if (tok == NULL) {
+           strange_header(file, buffer, buflen, _("<disk name>"), tok);
+           goto out;
+       }
+       uqname = unquote_string(tok);
+       strncpy(file->disk, uqname, SIZEOF(file->disk) - 1);
+       amfree(uqname);
        
-       if(file->type == F_SPLIT_DUMPFILE){
-           tok = strtok(NULL, " ");
-           if (tok == NULL || strcmp(tok, "part") != 0)
-               goto weird_header;
-
-           tok = strtok(NULL, "/");
-           if (tok == NULL || sscanf(tok, "%d", &file->partnum) != 1)
-               goto weird_header;
-
-           tok = strtok(NULL, " ");
-           if (tok == NULL)
-               goto weird_header;
+       if(file->type == F_SPLIT_DUMPFILE) {
+           tok = strtok_r(NULL, " ", &saveptr);
+           if (tok == NULL || strcmp(tok, "part") != 0) {
+               strange_header(file, buffer, buflen, "part", tok);
+               goto out;
+           }
+
+           tok = strtok_r(NULL, "/", &saveptr);
+           if ((tok == NULL) || (sscanf(tok, "%d", &file->partnum) != 1)) {
+               strange_header(file, buffer, buflen, _("<part num param>"), tok);
+               goto out;
+           }
+
            /* If totalparts == -1, then the original dump was done in 
               streaming mode (no holding disk), thus we don't know how 
                many parts there are. */
-            if(sscanf(tok, "%d", &file->totalparts) != 1){
-               goto weird_header;
+           tok = strtok_r(NULL, " ", &saveptr);
+            if((tok == NULL) || (sscanf(tok, "%d", &file->totalparts) != 1)) {
+               strange_header(file, buffer, buflen, _("<total parts param>"), tok);
+               goto out;
            }
+       } else if (file->type == F_DUMPFILE) {
+           /* only one part in this dump, so call it partnum 1 */
+           file->partnum = 1;
+           file->totalparts = 1;
+       } else {
+           file->partnum = 0;
+           file->totalparts = 0;
+       }
+
+       tok = strtok_r(NULL, " ", &saveptr);
+       if ((tok == NULL) || (strcmp(tok, "lev") != 0)) {
+           strange_header(file, buffer, buflen, "lev", tok);
+           goto out;
        }
-       
 
-       tok = strtok(NULL, " ");
-       if (tok == NULL || strcmp(tok, "lev") != 0)
-           goto weird_header;
-
-       tok = strtok(NULL, " ");
-       if (tok == NULL || sscanf(tok, "%d", &file->dumplevel) != 1)
-           goto weird_header;
-
-       tok = strtok(NULL, " ");
-       if (tok == NULL || strcmp(tok, "comp") != 0)
-           goto weird_header;
-
-       tok = strtok(NULL, " ");
-       if (tok == NULL)
-           goto weird_header;
-       strncpy(file->comp_suffix, tok, sizeof(file->comp_suffix) - 1);
-
-       file->compressed = strcmp(file->comp_suffix, "N");
-       /* compatibility with pre-2.2 amanda */
-       if (strcmp(file->comp_suffix, "C") == 0)
-           strncpy(file->comp_suffix, ".Z", sizeof(file->comp_suffix) - 1);
-              
-       tok = strtok(NULL, " ");
+       tok = strtok_r(NULL, " ", &saveptr);
+       if ((tok == NULL) || (sscanf(tok, "%d", &file->dumplevel) != 1)) {
+           strange_header(file, buffer, buflen, _("<dump level param>"), tok);
+           goto out;
+       }
+
+       tok = strtok_r(NULL, " ", &saveptr);
+       if ((tok == NULL) || (strcmp(tok, "comp") != 0)) {
+           strange_header(file, buffer, buflen, "comp", tok);
+           goto out;
+       }
+
+       tok = strtok_r(NULL, " ", &saveptr);
+       if (tok == NULL) {
+           strange_header(file, buffer, buflen, _("<comp param>"), tok);
+           goto out;
+       }
+       strncpy(file->comp_suffix, tok, SIZEOF(file->comp_suffix) - 1);
+
+       file->compressed = (0 != strcmp(file->comp_suffix, "N"));
+       if (file->compressed) {
+           /* compatibility with pre-2.2 amanda */
+           if (strcmp(file->comp_suffix, "C") == 0)
+               strncpy(file->comp_suffix, ".Z", SIZEOF(file->comp_suffix) - 1);
+       } else {
+           strcpy(file->comp_suffix, "");
+       }
+
+       tok = strtok_r(NULL, " ", &saveptr);
         /* "program" is optional */
         if (tok == NULL || strcmp(tok, "program") != 0) {
-           amfree(buf);
-           amfree(line1);
-            return;
+           break;
        }
 
-        tok = strtok(NULL, " ");
-        if (tok == NULL)
-            goto weird_header;
-        strncpy(file->program, tok, sizeof(file->program) - 1);
-        if (file->program[0] == '\0')
-            strncpy(file->program, "RESTORE", sizeof(file->program) - 1);
+        tok = strtok_r(NULL, " ", &saveptr);
+        if (tok == NULL) {
+           strange_header(file, buffer, buflen, _("<program name>"), tok);
+           goto out;
+       }
+        strncpy(file->program, tok, SIZEOF(file->program) - 1);
 
-       if ((tok = strtok(NULL, " ")) == NULL)
-             break;          /* reach the end of the buf */
+       if ((tok = strtok_r(NULL, " ", &saveptr)) == NULL)
+             break;          /* reached the end of the buffer */
 
-       /* "encryption" is optional */
+       /* encryption is optional */
        if (BSTRNCMP(tok, "crypt") == 0) {
-           tok = strtok(NULL, " ");
-           if (tok == NULL)
-               goto weird_header;
+           tok = strtok_r(NULL, " ", &saveptr);
+           if (tok == NULL) {
+               strange_header(file, buffer, buflen, _("<crypt param>"), tok);
+               goto out;
+           }
            strncpy(file->encrypt_suffix, tok,
-                   sizeof(file->encrypt_suffix) - 1);
-           file->encrypted = BSTRNCMP(file->encrypt_suffix, "N");
-           if ((tok = strtok(NULL, " ")) == NULL)
+                   SIZEOF(file->encrypt_suffix) - 1);
+           file->encrypted = 1;
+
+           /* for compatibility with who-knows-what, allow "comp N" to be
+            * equivalent to no compression */
+           if (0 == BSTRNCMP(file->encrypt_suffix, "N")) {
+               file->encrypted = 0;
+               strcpy(file->encrypt_suffix, "");
+           }
+
+           if ((tok = strtok_r(NULL, " ", &saveptr)) == NULL)
                break;
        }
 
        /* "srvcompprog" is optional */
        if (BSTRNCMP(tok, "server_custom_compress") == 0) {
-           tok = strtok(NULL, " ");
-           if (tok == NULL)
-               goto weird_header;
-           strncpy(file->srvcompprog, tok, sizeof(file->srvcompprog) - 1);
-           if ((tok = strtok(NULL, " ")) == NULL)
+           tok = strtok_r(NULL, " ", &saveptr);
+           if (tok == NULL) {
+               strange_header(file, buffer, buflen,
+                               _("<server custom compress param>"), tok);
+               goto out;
+           }
+           strncpy(file->srvcompprog, tok, SIZEOF(file->srvcompprog) - 1);
+           if ((tok = strtok_r(NULL, " ", &saveptr)) == NULL)
                break;      
        }
 
        /* "clntcompprog" is optional */
        if (BSTRNCMP(tok, "client_custom_compress") == 0) {
-           tok = strtok(NULL, " ");
-           if (tok == NULL)
-               goto weird_header;
-           strncpy(file->clntcompprog, tok, sizeof(file->clntcompprog) - 1);
-           if ((tok = strtok(NULL, " ")) == NULL)
+           tok = strtok_r(NULL, " ", &saveptr);
+           if (tok == NULL) {
+               strange_header(file, buffer, buflen,
+                               _("<client custom compress param>"), tok);
+               goto out;
+           }
+           strncpy(file->clntcompprog, tok, SIZEOF(file->clntcompprog) - 1);
+           if ((tok = strtok_r(NULL, " ", &saveptr)) == NULL)
                break;
        }
 
        /* "srv_encrypt" is optional */
        if (BSTRNCMP(tok, "server_encrypt") == 0) {
-           tok = strtok(NULL, " ");
-           if (tok == NULL)
-               goto weird_header;
-           strncpy(file->srv_encrypt, tok, sizeof(file->srv_encrypt) - 1);
-           if ((tok = strtok(NULL, " ")) == NULL) 
+           tok = strtok_r(NULL, " ", &saveptr);
+           if (tok == NULL) {
+               strange_header(file, buffer, buflen,
+                               _("<server encrypt param>"), tok);
+               goto out;
+           }
+           strncpy(file->srv_encrypt, tok, SIZEOF(file->srv_encrypt) - 1);
+           if ((tok = strtok_r(NULL, " ", &saveptr)) == NULL) 
                break;
        }
 
        /* "clnt_encrypt" is optional */
        if (BSTRNCMP(tok, "client_encrypt") == 0) {
-           tok = strtok(NULL, " ");
-           if (tok == NULL)
-               goto weird_header;
-           strncpy(file->clnt_encrypt, tok, sizeof(file->clnt_encrypt) - 1);
-           if ((tok = strtok(NULL, " ")) == NULL) 
+           tok = strtok_r(NULL, " ", &saveptr);
+           if (tok == NULL) {
+               strange_header(file, buffer, buflen,
+                               _("<client encrypt param>"), tok);
+               goto out;
+           }
+           strncpy(file->clnt_encrypt, tok, SIZEOF(file->clnt_encrypt) - 1);
+           if ((tok = strtok_r(NULL, " ", &saveptr)) == NULL) 
                break;
        }
 
        /* "srv_decrypt_opt" is optional */
        if (BSTRNCMP(tok, "server_decrypt_option") == 0) {
-           tok = strtok(NULL, " ");
-           if (tok == NULL)
-               goto weird_header;
+           tok = strtok_r(NULL, " ", &saveptr);
+           if (tok == NULL) {
+               strange_header(file, buffer, buflen,
+                               _("<server decrypt param>"), tok);
+               goto out;
+           }
            strncpy(file->srv_decrypt_opt, tok,
-                   sizeof(file->srv_decrypt_opt) - 1);
-           if ((tok = strtok(NULL, " ")) == NULL) 
+                   SIZEOF(file->srv_decrypt_opt) - 1);
+           if ((tok = strtok_r(NULL, " ", &saveptr)) == NULL) 
                break;
        }
 
        /* "clnt_decrypt_opt" is optional */
        if (BSTRNCMP(tok, "client_decrypt_option") == 0) {
-           tok = strtok(NULL, " ");
-           if (tok == NULL)
-               goto weird_header;
+           tok = strtok_r(NULL, " ", &saveptr);
+           if (tok == NULL) {
+               strange_header(file, buffer, buflen,
+                               _("<client decrypt param>"), tok);
+               goto out;
+           }
            strncpy(file->clnt_decrypt_opt, tok,
-                   sizeof(file->clnt_decrypt_opt) - 1);
-           if ((tok = strtok(NULL, " ")) == NULL) 
+                   SIZEOF(file->clnt_decrypt_opt) - 1);
+           if ((tok = strtok_r(NULL, " ", &saveptr)) == NULL) 
                break;
        }
       break;
       
 
     case F_TAPEEND:
-       tok = strtok(NULL, " ");
+       tok = strtok_r(NULL, " ", &saveptr);
        /* DATE is optional */
-       if (tok == NULL || strcmp(tok, "DATE") != 0) {
-           amfree(buf);
-           amfree(line1);
-           return;
+       if (tok != NULL) {
+           if (strcmp(tok, "DATE") == 0) {
+               tok = strtok_r(NULL, " ", &saveptr);
+               if(tok == NULL)
+                   file->datestamp[0] = '\0';
+               else
+                   strncpy(file->datestamp, tok, SIZEOF(file->datestamp) - 1);
+           } else {
+               strange_header(file, buffer, buflen, _("<DATE>"), tok);
+          }
+       } else {
+           file->datestamp[0] = '\0';
        }
-       strncpy(file->datestamp, tok, sizeof(file->datestamp) - 1);
+       break;
+
+    case F_NOOP:
+       /* nothing follows */
        break;
 
     default:
-       goto weird_header;
+       strange_header(file, buffer, buflen,
+               _("TAPESTART|DUMPFILE|CONT_DUMPFILE|SPLIT_DUMPFILE|TAPEEND|NOOP"), tok);
+       goto out;
     }
 
-    line = strtok(buf, "\n"); /* this is the first line */
+    (void)strtok_r(buf, "\n", &saveptr); /* this is the first line */
     /* iterate through the rest of the lines */
-    while ((line = strtok(NULL, "\n")) != NULL) {
+    while ((line = strtok_r(NULL, "\n", &saveptr)) != NULL) {
 #define SC "CONT_FILENAME="
-       if (strncmp(line, SC, sizeof(SC) - 1) == 0) {
-           line += sizeof(SC) - 1;
+       if (strncmp(line, SC, SIZEOF(SC) - 1) == 0) {
+           line += SIZEOF(SC) - 1;
            strncpy(file->cont_filename, line,
-                   sizeof(file->cont_filename) - 1);
-                   continue;
+                   SIZEOF(file->cont_filename) - 1);
+           continue;
        }
 #undef SC
 
 #define SC "PARTIAL="
-       if (strncmp(line, SC, sizeof(SC) - 1) == 0) {
-           line += sizeof(SC) - 1;
+       if (strncmp(line, SC, SIZEOF(SC) - 1) == 0) {
+           line += SIZEOF(SC) - 1;
            file->is_partial = !strcasecmp(line, "yes");
            continue;
        }
 #undef SC
+#define SC "APPLICATION="
+       if (strncmp(line, SC, SIZEOF(SC) - 1) == 0) {
+           line += SIZEOF(SC) - 1;
+           strncpy(file->application, line,
+                   SIZEOF(file->application) - 1);
+           continue;
+       }
+#undef SC
 
-#define SC "To restore, position tape at start of file and run:"
-       if (strncmp(line, SC, sizeof(SC) - 1) == 0)
+#define SC "ORIGSIZE="
+       if (strncmp(line, SC, SIZEOF(SC) - 1) == 0) {
+           line += SIZEOF(SC) - 1;
+           file->orig_size = OFF_T_ATOI(line);
+       }
+#undef SC
+
+#define SC "DLE="
+       if (strncmp(line, SC, SIZEOF(SC) - 1) == 0) {
+           line += SIZEOF(SC) - 1;
+           file->dle_str = parse_heredoc(line, &saveptr);
+       }
+#undef SC
+
+#define SC _("To restore, position tape at start of file and run:")
+       if (strncmp(line, SC, SIZEOF(SC) - 1) == 0)
            continue;
 #undef SC
 
-#define SC "\tdd if=<tape> bs="
-       if (strncmp(line, SC, sizeof(SC) - 1) == 0) {
-           char *cmd1=NULL, *cmd2=NULL, *cmd3=NULL;
+#define SC "\tdd if=<tape> "
+       if (strncmp(line, SC, SIZEOF(SC) - 1) == 0) {
+           char *cmd1, *cmd2, *cmd3=NULL;
 
            /* skip over dd command */
            if ((cmd1 = strchr(line, '|')) == NULL) {
 
                strncpy(file->recover_cmd, "BUG",
-                       sizeof(file->recover_cmd) - 1);
+                       SIZEOF(file->recover_cmd) - 1);
                continue;
            }
            *cmd1++ = '\0';
@@ -314,6 +485,11 @@ parse_file_header(buffer, file, buflen)
                *cmd3++ = '\0';
            }
           
+           /* clean up some extra spaces in various fields */
+           chomp(cmd1);
+           chomp(cmd2);
+           chomp(cmd3);
+
            /* three cmds: decrypt    | uncompress | recover
             * two   cmds: uncompress | recover
             * XXX note that if there are two cmds, the first one 
@@ -323,241 +499,440 @@ parse_file_header(buffer, file, buflen)
             * one   cmds: recover
             */
 
-           if (cmd3 == NULL) {
+           if ( cmd3 == NULL) {
              if (cmd2 == NULL) {
                strncpy(file->recover_cmd, cmd1,
-                       sizeof(file->recover_cmd) - 1);
+                       SIZEOF(file->recover_cmd) - 1);
              } else {
-               snprintf(file->uncompress_cmd,
-                        sizeof(file->uncompress_cmd), "%s|", cmd1);
+               g_snprintf(file->uncompress_cmd,
+                        SIZEOF(file->uncompress_cmd), "%s |", cmd1);
                strncpy(file->recover_cmd, cmd2,
-                       sizeof(file->recover_cmd) - 1);
+                       SIZEOF(file->recover_cmd) - 1);
              }
            } else {    /* cmd3 presents:  decrypt | uncompress | recover */
-             snprintf(file->decrypt_cmd,
-                      sizeof(file->decrypt_cmd), "%s|", cmd1);
-             snprintf(file->uncompress_cmd,
-                      sizeof(file->uncompress_cmd), "%s|", cmd2);
+             g_snprintf(file->decrypt_cmd,
+                      SIZEOF(file->decrypt_cmd), "%s |", cmd1);
+             g_snprintf(file->uncompress_cmd,
+                      SIZEOF(file->uncompress_cmd), "%s |", cmd2);
              strncpy(file->recover_cmd, cmd3,
-                     sizeof(file->recover_cmd) - 1);
+                     SIZEOF(file->recover_cmd) - 1);
            }
            continue;
        }
 #undef SC
        /* XXX complain about weird lines? */
     }
-    amfree(buf);
-    amfree(line1);
-    return;
 
-weird_header:
-    fprintf(stderr, "%s: strange amanda header: \"%.*s\"\n", get_pname(),
-       (int) buflen, buffer);
-    file->type = F_WEIRD;
+out:
     amfree(buf);
     amfree(line1);
 }
 
 void
-build_header(buffer, file, buflen)
-    char *buffer;
-    const dumpfile_t *file;
-    size_t buflen;
+dump_dumpfile_t(
+    const dumpfile_t *file)
 {
-    int n;
-    char split_data[128] = "";
+       g_debug(_("Contents of *(dumpfile_t *)%p:"), file);
+       g_debug(_("    type             = %d (%s)"),
+                       file->type, filetype2str(file->type));
+       g_debug(_("    datestamp        = '%s'"), file->datestamp);
+       g_debug(_("    dumplevel        = %d"), file->dumplevel);
+       g_debug(_("    compressed       = %d"), file->compressed);
+       g_debug(_("    encrypted        = %d"), file->encrypted);
+       g_debug(_("    comp_suffix      = '%s'"), file->comp_suffix);
+       g_debug(_("    encrypt_suffix   = '%s'"), file->encrypt_suffix);
+       g_debug(_("    name             = '%s'"), file->name);
+       g_debug(_("    disk             = '%s'"), file->disk);
+       g_debug(_("    program          = '%s'"), file->program);
+       g_debug(_("    application      = '%s'"), file->application);
+       g_debug(_("    srvcompprog      = '%s'"), file->srvcompprog);
+       g_debug(_("    clntcompprog     = '%s'"), file->clntcompprog);
+       g_debug(_("    srv_encrypt      = '%s'"), file->srv_encrypt);
+       g_debug(_("    clnt_encrypt     = '%s'"), file->clnt_encrypt);
+       g_debug(_("    recover_cmd      = '%s'"), file->recover_cmd);
+       g_debug(_("    uncompress_cmd   = '%s'"), file->uncompress_cmd);
+       g_debug(_("    decrypt_cmd      = '%s'"), file->decrypt_cmd);
+       g_debug(_("    srv_decrypt_opt  = '%s'"), file->srv_decrypt_opt);
+       g_debug(_("    clnt_decrypt_opt = '%s'"), file->clnt_decrypt_opt);
+       g_debug(_("    cont_filename    = '%s'"), file->cont_filename);
+       if (file->dle_str)
+           g_debug(_("    dle_str          = %s"), file->dle_str);
+       else
+           g_debug(_("    dle_str          = (null)"));
+       g_debug(_("    is_partial       = %d"), file->is_partial);
+       g_debug(_("    partnum          = %d"), file->partnum);
+       g_debug(_("    totalparts       = %d"), file->totalparts);
+       if (file->blocksize)
+           g_debug(_("    blocksize        = %zu"), file->blocksize);
+}
 
-    memset(buffer,'\0',buflen);
+static void
+validate_nonempty_str(
+    const char *val,
+    const char *name)
+{
+    if (strlen(val) == 0) {
+       error(_("Invalid %s '%s'\n"), name, val);
+       /*NOTREACHED*/
+    }
+}
+
+static void
+validate_not_both(
+    const char *val1, const char *val2,
+    const char *name1, const char *name2)
+{
+    if (*val1 && *val2) {
+       error("cannot set both %s and %s\n", name1, name2);
+    }
+}
+
+static void
+validate_no_space(
+    const char *val,
+    const char *name)
+{
+    if (strchr(val, ' ') != NULL) {
+       error(_("%s cannot contain spaces\n"), name);
+       /*NOTREACHED*/
+    }
+}
+
+static void
+validate_pipe_cmd(
+       const char *cmd,
+       const char *name)
+{
+    if (strlen(cmd) && cmd[strlen(cmd)-1] != '|') {
+       error("invalid %s (must end with '|'): '%s'\n", name, cmd);
+    }
+}
+
+static void
+validate_encrypt_suffix(
+       int encrypted,
+       const char *suff)
+{
+    if (encrypted) {
+       if (!suff[0] || (0 == strcmp(suff, "N"))) {
+           error(_("Invalid encrypt_suffix '%s'\n"), suff);
+       }
+    } else {
+       if (suff[0] && (0 != strcmp(suff, "N"))) {
+           error(_("Invalid header: encrypt_suffix '%s' specified but not encrypted\n"), suff);
+       }
+    }
+}
+
+static void
+validate_datestamp(
+    const char *datestamp)
+{
+       if (strcmp(datestamp, "X") == 0) {
+           return;
+       }
+
+       if ((strlen(datestamp) == 8) && match("^[0-9]{8}$", datestamp)) {
+           return;
+       }
+       if ((strlen(datestamp) == 14) && match("^[0-9]{14}$", datestamp)) {
+           return;
+       }
+       error(_("Invalid datestamp '%s'\n"), datestamp);
+       /*NOTREACHED*/
+}
+
+static void
+validate_parts(
+    const int partnum,
+    const int totalparts)
+{
+       if (partnum < 1) {
+           error(_("Invalid partnum (%d)\n"), partnum);
+           /*NOTREACHED*/
+       }
+
+       if (partnum > totalparts && totalparts >= 0) {
+           error(_("Invalid partnum (%d) > totalparts (%d)\n"),
+                       partnum, totalparts);
+           /*NOTREACHED*/
+       }
+}
+
+char *
+build_header(const dumpfile_t * file, size_t *size, size_t max_size)
+{
+    GString *rval, *split_data;
+    char *qname;
+    char *program;
+    size_t min_size;
+
+    min_size = size? *size : max_size;
+    g_debug(_("Building type %s header of %zu-%zu bytes with name='%s' disk='%s' dumplevel=%d and blocksize=%zu"),
+           filetype2str(file->type), min_size, max_size,
+           file->name, file->disk, file->dumplevel, file->blocksize);
+
+    rval = g_string_sized_new(min_size);
+    split_data = g_string_sized_new(10);
 
     switch (file->type) {
     case F_TAPESTART:
-       snprintf(buffer, buflen,
-           "AMANDA: TAPESTART DATE %s TAPE %s\n014\n",
-           file->datestamp, file->name);
+       validate_nonempty_str(file->name, "name");
+       validate_datestamp(file->datestamp);
+        g_string_printf(rval,
+                        "AMANDA: TAPESTART DATE %s TAPE %s\n\014\n",
+                        file->datestamp, file->name);
        break;
 
     case F_SPLIT_DUMPFILE:
-       snprintf(split_data, sizeof(split_data),
-                " part %d/%d ", file->partnum, file->totalparts);
-    /* FALLTHROUGH */
-       
+       validate_parts(file->partnum, file->totalparts);
+        g_string_printf(split_data,
+                        " part %d/%d ", file->partnum, file->totalparts);
+        /* FALLTHROUGH */
+
     case F_CONT_DUMPFILE:
     case F_DUMPFILE :
-        n = snprintf(buffer, buflen,
-                     "AMANDA: %s %s %s %s %s lev %d comp %s program %s",
-                        filetype2str(file->type),
-                        file->datestamp, file->name, file->disk,
-                        split_data,
-                        file->dumplevel, file->comp_suffix, file->program); 
-       if ( n ) {
-         buffer += n;
-         buflen -= n;
-         n = 0;
-       }
-     
-       if (strcmp(file->encrypt_suffix, "enc") == 0) {  /* only output crypt if it's enabled */
-         n = snprintf(buffer, buflen, " crypt %s", file->encrypt_suffix);
-       }
-       if ( n ) {
-         buffer += n;
-         buflen -= n;
-         n = 0;
+       validate_nonempty_str(file->name, "name");
+       validate_nonempty_str(file->program, "program");
+       validate_datestamp(file->datestamp);
+       validate_encrypt_suffix(file->encrypted, file->encrypt_suffix);
+       qname = quote_string(file->disk);
+       program = stralloc(file->program);
+       if (match("^.*[.][Ee][Xx][Ee]$", program)) {
+               /* Trim ".exe" from program name */
+               program[strlen(program) - strlen(".exe")] = '\0';
+       }
+        g_string_printf(rval, 
+                        "AMANDA: %s %s %s %s %s lev %d comp %s program %s",
+                        filetype2str(file->type),
+                        file->datestamp, file->name, qname,
+                        split_data->str,
+                        file->dumplevel,
+                       file->compressed? file->comp_suffix : "N",
+                       program); 
+       amfree(program);
+       amfree(qname);
+
+        /* only output crypt if it's enabled */
+       if (file->encrypted) {
+           g_string_append_printf(rval, " crypt %s", file->encrypt_suffix);
        }
 
+       validate_not_both(file->srvcompprog, file->clntcompprog,
+                           "srvcompprog", "clntcompprog");
        if (*file->srvcompprog) {
-           n = snprintf(buffer, buflen, " server_custom_compress %s", file->srvcompprog);
+           validate_no_space(file->srvcompprog, "srvcompprog");
+            g_string_append_printf(rval, " server_custom_compress %s",
+                                   file->srvcompprog);
        } else if (*file->clntcompprog) {
-           n = snprintf(buffer, buflen, " client_custom_compress %s", file->clntcompprog);
-       } 
-
-       if ( n ) {
-         buffer += n;
-         buflen -= n;
-         n = 0;
+           validate_no_space(file->clntcompprog, "clntcompprog");
+            g_string_append_printf(rval, " client_custom_compress %s",
+                                   file->clntcompprog);
        }
 
+       validate_not_both(file->srv_encrypt, file->clnt_encrypt,
+                           "srv_encrypt", "clnt_encrypt");
        if (*file->srv_encrypt) {
-           n = snprintf(buffer, buflen, " server_encrypt %s", file->srv_encrypt);
+           validate_no_space(file->srv_encrypt, "srv_encrypt");
+            g_string_append_printf(rval, " server_encrypt %s",
+                                   file->srv_encrypt);
        } else if (*file->clnt_encrypt) {
-           n = snprintf(buffer, buflen, " client_encrypt %s", file->clnt_encrypt);
-       } 
-
-       if ( n ) {
-         buffer += n;
-         buflen -= n;
-         n = 0;
+           validate_no_space(file->clnt_encrypt, "clnt_encrypt");
+            g_string_append_printf(rval, " client_encrypt %s",
+                                   file->clnt_encrypt);
        }
-       
+
+       validate_not_both(file->srv_decrypt_opt, file->clnt_decrypt_opt,
+                           "srv_decrypt_opt", "clnt_decrypt_opt");
        if (*file->srv_decrypt_opt) {
-           n = snprintf(buffer, buflen, " server_decrypt_option %s", file->srv_decrypt_opt);
-       } else if (*file->clnt_decrypt_opt) {
-           n = snprintf(buffer, buflen, " client_decrypt_option %s", file->clnt_decrypt_opt);
+           validate_no_space(file->srv_decrypt_opt, "srv_decrypt_opt");
+            g_string_append_printf(rval, " server_decrypt_option %s",
+                                   file->srv_decrypt_opt);
+        } else if (*file->clnt_decrypt_opt) {
+            g_string_append_printf(rval, " client_decrypt_option %s",
+                                   file->clnt_decrypt_opt);
        } 
-
-       if ( n ) {
-         buffer += n;
-         buflen -= n;
-         n = 0;
-       }
-
-       n = snprintf(buffer, buflen, "\n");
-       buffer += n;
-       buflen -= n;
-
+        
+        g_string_append_printf(rval, "\n");
+        
        if (file->cont_filename[0] != '\0') {
-           n = snprintf(buffer, buflen, "CONT_FILENAME=%s\n",
-               file->cont_filename);
-           buffer += n;
-           buflen -= n;
+            g_string_append_printf(rval, "CONT_FILENAME=%s\n",
+                                   file->cont_filename);
+       }
+       if (file->application[0] != '\0') {
+            g_string_append_printf(rval, "APPLICATION=%s\n", file->application);
        }
        if (file->is_partial != 0) {
-           n = snprintf(buffer, buflen, "PARTIAL=YES\n");
-           buffer += n;
-           buflen -= n;
-       }
-
-       n = snprintf(buffer, buflen, 
-           "To restore, position tape at start of file and run:\n");
-       buffer += n;
-       buflen -= n;
-
-       /* \014 == ^L */
-       n = snprintf(buffer, buflen,
-           "\tdd if=<tape> bs=%ldk skip=1 |%s %s %s\n\014\n",
-           file->blocksize / 1024, file->decrypt_cmd, file->uncompress_cmd, file->recover_cmd);
-       buffer += n;
-       buflen -= n;
+            g_string_append_printf(rval, "PARTIAL=YES\n");
+       }
+       if (file->orig_size > 0) {
+           g_string_append_printf(rval, "ORIGSIZE=%jd\n",
+                                        (intmax_t)file->orig_size);
+       }
+       if (file->dle_str && strlen(file->dle_str) < max_size-2048) {
+           char *heredoc = quote_heredoc(file->dle_str, "ENDDLE");
+           g_string_append_printf(rval, "DLE=%s\n", heredoc);
+           amfree(heredoc);
+       }
+        
+        g_string_append_printf(rval,
+           _("To restore, position tape at start of file and run:\n"));
+
+        g_string_append_printf(rval, "\tdd if=<tape> ");
+       if (file->blocksize)
+           g_string_append_printf(rval, "bs=%zuk ",
+                                  file->blocksize / 1024);
+       g_string_append_printf(rval, "skip=1 | ");
+       if (*file->recover_cmd) {
+           if (*file->decrypt_cmd) {
+               validate_pipe_cmd(file->decrypt_cmd, "decrypt_cmd");
+               g_string_append_printf(rval, "%s ", file->decrypt_cmd);
+           }
+           if (*file->uncompress_cmd) {
+               validate_pipe_cmd(file->uncompress_cmd, "uncompress_cmd");
+               g_string_append_printf(rval, "%s ", file->uncompress_cmd);
+           }
+           g_string_append_printf(rval, "%s ", file->recover_cmd);
+       } else {
+           if (*file->uncompress_cmd || *file->decrypt_cmd)
+               error("cannot specify uncompress_cmd or decrypt_cmd without recover_cmd\n");
+       }
+       /* \014 == ^L == form feed */
+       g_string_append_printf(rval, "\n\014\n");
        break;
 
     case F_TAPEEND:
-       snprintf(buffer, buflen, "AMANDA: TAPEEND DATE %s\n\014\n",
-           file->datestamp);
+       validate_datestamp(file->datestamp);
+        g_string_printf(rval, "AMANDA: TAPEEND DATE %s\n\014\n",
+                        file->datestamp);
+       break;
+
+    case F_NOOP:
+        g_string_printf(rval, "AMANDA: NOOP\n\014\n");
        break;
 
     case F_UNKNOWN:
+    case F_EMPTY:
     case F_WEIRD:
-       break;
+    default:
+       error(_("Invalid header type: %d (%s)"),
+               file->type, filetype2str(file->type));
+       /*NOTREACHED*/
+    }
+    
+    g_string_free(split_data, TRUE);
+
+    /* is it too big? */
+    if (rval->len > max_size) {
+       g_debug("header is larger than %zu bytes -- cannot create", max_size);
+       g_string_free(rval, TRUE);
+       return NULL;
+    }
+
+    /* Clear extra bytes. */
+    if (rval->len < min_size) {
+        bzero(rval->str + rval->len, rval->allocated_len - rval->len);
+    }
+    if (size) {
+       *size = MAX(min_size, (size_t)rval->len);
     }
+    return g_string_free(rval, FALSE);
+}
+
+void
+print_header(
+    FILE *             outf,
+    const dumpfile_t * file)
+{
+    char *summ = summarize_header(file);
+    g_fprintf(outf, "%s\n", summ);
+    g_free(summ);
 }
 
 /*
  * Prints the contents of the file structure.
  */
-void
-print_header(outf, file)
-    FILE *outf;
-    const dumpfile_t *file;
+char *
+summarize_header(
+    const dumpfile_t * file)
 {
-    char number[NUM_STR_SIZE*2];
+    char *qdisk;
+    GString *summ;
+
     switch(file->type) {
+    case F_EMPTY:
+       return g_strdup(_("EMPTY file"));
+
     case F_UNKNOWN:
-       fprintf(outf, "UNKNOWN file\n");
-       break;
+       return g_strdup(_("UNKNOWN file"));
+
+    default:
     case F_WEIRD:
-       fprintf(outf, "WEIRD file\n");
-       break;
+       return g_strdup(_("WEIRD file"));
+
     case F_TAPESTART:
-       fprintf(outf, "start of tape: date %s label %s\n",
+       return g_strdup_printf(_("start of tape: date %s label %s"),
               file->datestamp, file->name);
-       break;
+
+    case F_NOOP:
+       return g_strdup(_("NOOP file"));
+
     case F_DUMPFILE:
     case F_CONT_DUMPFILE:
-       fprintf(outf, "%s: date %s host %s disk %s lev %d comp %s",
+       qdisk = quote_string(file->disk);
+       summ = g_string_new("");
+        g_string_printf(summ, "%s: date %s host %s disk %s lev %d comp %s",
            filetype2str(file->type), file->datestamp, file->name,
-           file->disk, file->dumplevel, file->comp_suffix);
+           qdisk, file->dumplevel,
+           file->compressed? file->comp_suffix : "N");
+       amfree(qdisk);
+       goto add_suffixes;
+
+    case F_SPLIT_DUMPFILE: {
+       char totalparts[NUM_STR_SIZE*2];
+        if(file->totalparts > 0)
+            g_snprintf(totalparts, SIZEOF(totalparts), "%d", file->totalparts);
+        else
+           g_snprintf(totalparts, SIZEOF(totalparts), "UNKNOWN");
+       qdisk = quote_string(file->disk);
+        summ = g_string_new("");
+        g_string_printf(summ, "split dumpfile: date %s host %s disk %s"
+                     " part %d/%s lev %d comp %s",
+                      file->datestamp, file->name, qdisk, file->partnum,
+                      totalparts, file->dumplevel,
+                     file->compressed? file->comp_suffix : "N");
+       amfree(qdisk);
+        goto add_suffixes;
+    }
+
+    add_suffixes:
        if (*file->program)
-           fprintf(outf, " program %s",file->program);
-       if (strcmp(file->encrypt_suffix, "enc") == 0)
-           fprintf(outf, " crypt %s", file->encrypt_suffix);
-       if (*file->srvcompprog)
-           fprintf(outf, " server_custom_compress %s", file->srvcompprog);
-       if (*file->clntcompprog)
-           fprintf(outf, " client_custom_compress %s", file->clntcompprog);
-       if (*file->srv_encrypt)
-           fprintf(outf, " server_encrypt %s", file->srv_encrypt);
-       if (*file->clnt_encrypt)
-           fprintf(outf, " client_encrypt %s", file->clnt_encrypt);
-       if (*file->srv_decrypt_opt)
-           fprintf(outf, " server_decrypt_option %s", file->srv_decrypt_opt);
-       if (*file->clnt_decrypt_opt)
-           fprintf(outf, " client_decrypt_option %s", file->clnt_decrypt_opt);
-       fprintf(outf, "\n");
-       break;
-    case F_SPLIT_DUMPFILE:
-        if(file->totalparts > 0){
-            snprintf(number, sizeof(number), "%d", file->totalparts);
-        }   
-        else snprintf(number, sizeof(number), "UNKNOWN");
-        fprintf(outf, "split dumpfile: date %s host %s disk %s part %d/%s lev %d comp %s",
-                      file->datestamp, file->name, file->disk, file->partnum,
-                      number, file->dumplevel, file->comp_suffix);
-        if (*file->program)
-            fprintf(outf, " program %s",file->program);
+           g_string_append_printf(summ, " program %s", file->program);
        if (strcmp(file->encrypt_suffix, "enc") == 0)
-           fprintf(outf, " crypt %s", file->encrypt_suffix);
+           g_string_append_printf(summ, " crypt %s", file->encrypt_suffix);
        if (*file->srvcompprog)
-           fprintf(outf, " server_custom_compress %s", file->srvcompprog);
+           g_string_append_printf(summ, " server_custom_compress %s", file->srvcompprog);
        if (*file->clntcompprog)
-           fprintf(outf, " client_custom_compress %s", file->clntcompprog);
+           g_string_append_printf(summ, " client_custom_compress %s", file->clntcompprog);
        if (*file->srv_encrypt)
-           fprintf(outf, " server_encrypt %s", file->srv_encrypt);
+           g_string_append_printf(summ, " server_encrypt %s", file->srv_encrypt);
        if (*file->clnt_encrypt)
-           fprintf(outf, " client_encrypt %s", file->clnt_encrypt);
+           g_string_append_printf(summ, " client_encrypt %s", file->clnt_encrypt);
        if (*file->srv_decrypt_opt)
-           fprintf(outf, " server_decrypt_option %s", file->srv_decrypt_opt);
+           g_string_append_printf(summ, " server_decrypt_option %s", file->srv_decrypt_opt);
        if (*file->clnt_decrypt_opt)
-           fprintf(outf, " client_decrypt_option %s", file->clnt_decrypt_opt);
-        fprintf(outf, "\n");
-        break;
+           g_string_append_printf(summ, " client_decrypt_option %s", file->clnt_decrypt_opt);
+       return g_string_free(summ, FALSE);
+
     case F_TAPEEND:
-       fprintf(outf, "end of tape: date %s\n", file->datestamp);
+       return g_strdup_printf("end of tape: date %s", file->datestamp);
        break;
     }
 }
 
 int
-known_compress_type(file)
-    const dumpfile_t *file;
+known_compress_type(
+    const dumpfile_t * file)
 {
     if(strcmp(file->comp_suffix, ".Z") == 0)
        return 1;
@@ -580,30 +955,163 @@ static const struct {
     { F_TAPEEND,  "TAPEEND" },
     { F_DUMPFILE, "FILE" },
     { F_CONT_DUMPFILE, "CONT_FILE" },
-    { F_SPLIT_DUMPFILE, "SPLIT_FILE" }
+    { F_SPLIT_DUMPFILE, "SPLIT_FILE" },
+    { F_NOOP, "NOOP" }
 };
-#define        NFILETYPES      (sizeof(filetypetab) / sizeof(filetypetab[0]))
+#define        NFILETYPES      (size_t)(sizeof(filetypetab) / sizeof(filetypetab[0]))
 
 static const char *
-filetype2str(type)
-    filetype_t type;
+filetype2str(
+    filetype_t type)
 {
     int i;
 
-    for (i = 0; i < NFILETYPES; i++)
+    for (i = 0; i < (int)NFILETYPES; i++)
        if (filetypetab[i].type == type)
            return (filetypetab[i].str);
     return ("UNKNOWN");
 }
 
 static filetype_t
-str2filetype(str)
-    const char *str;
+str2filetype(
+    const char *str)
 {
     int i;
 
-    for (i = 0; i < NFILETYPES; i++)
+    for (i = 0; i < (int)NFILETYPES; i++)
        if (strcmp(filetypetab[i].str, str) == 0)
            return (filetypetab[i].type);
     return (F_UNKNOWN);
 }
+
+gboolean headers_are_equal(dumpfile_t * a, dumpfile_t * b) {
+    if (a == NULL && b == NULL)
+        return TRUE;
+
+    if (a == NULL || b == NULL)
+        return FALSE;
+
+    if (a->type != b->type) return FALSE;
+    if (strcmp(a->datestamp, b->datestamp)) return FALSE;
+    if (a->dumplevel != b->dumplevel) return FALSE;
+    if (a->compressed != b->compressed) return FALSE;
+    if (a->encrypted != b->encrypted) return FALSE;
+    if (strcmp(a->comp_suffix, b->comp_suffix)) return FALSE;
+    if (strcmp(a->encrypt_suffix, b->encrypt_suffix)) return FALSE;
+    if (strcmp(a->name, b->name)) return FALSE;
+    if (strcmp(a->disk, b->disk)) return FALSE;
+    if (strcmp(a->program, b->program)) return FALSE;
+    if (strcmp(a->application, b->application)) return FALSE;
+    if (strcmp(a->srvcompprog, b->srvcompprog)) return FALSE;
+    if (strcmp(a->clntcompprog, b->clntcompprog)) return FALSE;
+    if (strcmp(a->srv_encrypt, b->srv_encrypt)) return FALSE;
+    if (strcmp(a->clnt_encrypt, b->clnt_encrypt)) return FALSE;
+    if (strcmp(a->recover_cmd, b->recover_cmd)) return FALSE;
+    if (strcmp(a->uncompress_cmd, b->uncompress_cmd)) return FALSE;
+    if (strcmp(a->decrypt_cmd, b->decrypt_cmd)) return FALSE;
+    if (strcmp(a->srv_decrypt_opt, b->srv_decrypt_opt)) return FALSE;
+    if (strcmp(a->clnt_decrypt_opt, b->clnt_decrypt_opt)) return FALSE;
+    if (strcmp(a->cont_filename, b->cont_filename)) return FALSE;
+    if (a->dle_str != b->dle_str && a->dle_str && b->dle_str
+       && strcmp(a->dle_str, b->dle_str)) return FALSE;
+    if (a->is_partial != b->is_partial) return FALSE;
+    if (a->partnum != b->partnum) return FALSE;
+    if (a->totalparts != b->totalparts) return FALSE;
+    if (a->blocksize != b->blocksize) return FALSE;
+
+    return TRUE; /* ok, they're the same */
+}
+
+dumpfile_t * dumpfile_copy(dumpfile_t* source) {
+    dumpfile_t* rval = malloc(sizeof(dumpfile_t));
+    memcpy(rval, source, sizeof(dumpfile_t));
+    if (rval->dle_str) rval->dle_str = stralloc(rval->dle_str);
+    return rval;
+}
+
+void
+dumpfile_copy_in_place(
+    dumpfile_t *dest,
+    dumpfile_t* source)
+{
+    memcpy(dest, source, sizeof(dumpfile_t));
+    if (dest->dle_str) dest->dle_str = stralloc(dest->dle_str);
+}
+
+void dumpfile_free_data(dumpfile_t* info) {
+    if (info) {
+       amfree(info->dle_str);
+    }
+}
+
+void dumpfile_free(dumpfile_t* info) {
+    dumpfile_free_data(info);
+    amfree(info);
+}
+
+static char *quote_heredoc(
+    char  *text,
+    char  *delimiter_prefix)
+{
+    char *delimiter = stralloc(delimiter_prefix);
+    int delimiter_n = 0;
+    int delimiter_len = strlen(delimiter);
+    char *quoted;
+
+    /* keep picking delimiters until we find one that's not a line in TEXT */
+    while (1) {
+       char *line = text;
+       char *c = text;
+       gboolean found_delimiter = FALSE;
+
+       while (1) {
+           if (*c == '\n' || *c == '\0') {
+               int linelen = c - line;
+               if (linelen == delimiter_len && 0 == strncmp(line, delimiter, linelen)) {
+                   found_delimiter = TRUE;
+                   break;
+               }
+               line = c+1;
+           }
+           if (!*c) break;
+           c++;
+       }
+
+       if (!found_delimiter)
+           break;
+
+       delimiter = newvstrallocf(delimiter, "%s%d", delimiter_prefix, ++delimiter_n);
+       delimiter_len = strlen(delimiter);
+    }
+
+    /* we have a delimiter .. now use it */
+    quoted = vstrallocf("<<%s\n%s\n%s", delimiter, text, delimiter);
+    amfree(delimiter);
+    return quoted;
+}
+
+static char *parse_heredoc(
+    char  *line,
+    char **saveptr)
+{
+    char *result = NULL;
+
+    if (strncmp(line, "<<", 2) == 0) {
+       char *keyword = line+2;
+       char *new_line;
+
+       while((new_line = strtok_r(NULL, "\n", saveptr)) != NULL &&
+             strcmp(new_line, keyword) != 0) {
+           result = vstrextend(&result, new_line, "\n", NULL);
+       }
+       /* make sure we have something */
+       if (!result)
+           result = g_strdup("");
+       /* remove latest '\n' */
+       else if (strlen(result) > 0)
+           result[strlen(result)-1] = '\0';
+    } else {
+       result = stralloc(line);
+    }
+    return result;
+}