X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=common-src%2Ffileheader.c;h=381c40d4a74bb6ded14a49b34e665817f193d028;hb=949b8910a5e23c4285d0b1aedacfc82a14dc97a5;hp=ede34793e14dd587bbf95051c86b35d857dc1266;hpb=1194fb66aa28d9929c3f2bef3cc6c1c3f40a60a4;p=debian%2Famanda diff --git a/common-src/fileheader.c b/common-src/fileheader.c index ede3479..381c40d 100644 --- a/common-src/fileheader.c +++ b/common-src/fileheader.c @@ -24,285 +24,456 @@ * 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 +#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 = ""; + if (expected == NULL) + expected = ""; + + 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, _(""), 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, _(""), 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, _(""), 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, _(""), 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, _(""), tok); + goto out; + } + strncpy(file->datestamp, tok, SIZEOF(file->datestamp) - 1); + + tok = strtok_r(NULL, " ", &saveptr); + if (tok == NULL) { + strange_header(file, buffer, buflen, _(""), tok); + goto out; + } + strncpy(file->name, tok, SIZEOF(file->name) - 1); + + tok = strquotedstr(&saveptr); + if (tok == NULL) { + strange_header(file, buffer, buflen, _(""), 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, _(""), 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, _(""), 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, _(""), 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, _(""), 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, _(""), 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, _(""), 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, + _(""), 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, + _(""), 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, + _(""), 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, + _(""), 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, + _(""), 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, + _(""), 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, _(""), 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= bs=" - if (strncmp(line, SC, sizeof(SC) - 1) == 0) { - char *cmd1=NULL, *cmd2=NULL, *cmd3=NULL; +#define SC "\tdd if= " + 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= 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= "); + 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; +}