/*
* Amanda, The Advanced Maryland Automatic Network Disk Archiver
* Copyright (c) 1991-1999 University of Maryland at College Park
* Copyright (c) 2007-2012 Zmanda, Inc. All Rights Reserved.
* Copyright (c) 2013-2016 Carbonite, Inc. All Rights Reserved.
* All Rights Reserved.
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of U.M. not be used in advertising or
* publicity pertaining to distribution of the software without specific,
* written prior permission. U.M. makes no representations about the
* suitability of this software for any purpose. It is provided "as is"
* without express or implied warranty.
*
* U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
* BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Authors: the Amanda Development Team. Its members are listed in a
* file named AUTHORS, in the root directory of this distribution.
*/
/*
* $Id: fileheader.c 6512 2007-05-24 17:00:24Z ian $
*/
#include "amanda.h"
#include "amutil.h"
#include "fileheader.h"
#include "match.h"
#include <glib.h>
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,
const char *message);
void
fh_init(
dumpfile_t *file)
{
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(
const char *buffer,
dumpfile_t *file,
size_t buflen)
{
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 = g_malloc(buflen + 1);
memcpy(buf, buffer, buflen);
buf[buflen] = '\0';
fh_init(file);
/* 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 = g_malloc(lsize + 1);
strncpy(line1, buf, lsize);
line1[lsize] = '\0';
*line = '\n';
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 (!g_str_equal(tok, "NETDUMP:") && !g_str_equal(tok, "AMANDA:")) {
amfree(buf);
file->type = F_WEIRD;
amfree(line1);
return;
}
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_r(NULL, " ", &saveptr);
if ((tok == NULL) || (!g_str_equal(tok, "DATE"))) {
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);
file->datestamp[sizeof(file->datestamp) - 1] = '\0';
tok = strtok_r(NULL, " ", &saveptr);
if ((tok == NULL) || (!g_str_equal(tok, "TAPE"))) {
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);
file->name[sizeof(file->name) - 1] = '\0';
break;
case F_DUMPFILE:
case F_CONT_DUMPFILE:
case F_SPLIT_DUMPFILE:
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);
file->datestamp[sizeof(file->datestamp) - 1] = '\0';
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);
file->name[sizeof(file->name) - 1] = '\0';
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);
file->disk[sizeof(file->disk) - 1] = '\0';
amfree(uqname);
if(file->type == F_SPLIT_DUMPFILE) {
tok = strtok_r(NULL, " ", &saveptr);
if (tok == NULL || !g_str_equal(tok, "part")) {
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. */
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) || (!g_str_equal(tok, "lev"))) {
strange_header(file, buffer, buflen, "lev", tok);
goto out;
}
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) || (!g_str_equal(tok, "comp"))) {
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 = (!g_str_equal(file->comp_suffix, "N"));
if (file->compressed) {
/* compatibility with pre-2.2 amanda */
if (g_str_equal(file->comp_suffix, "C"))
strncpy(file->comp_suffix, ".Z", sizeof(file->comp_suffix) - 1);
} else {
strcpy(file->comp_suffix, "");
}
file->comp_suffix[sizeof(file->comp_suffix) - 1] = '\0';
tok = strtok_r(NULL, " ", &saveptr);
/* "program" is optional */
if (tok == NULL || !g_str_equal(tok, "program")) {
break;
}
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);
file->program[sizeof(file->program) - 1] = '\0';
if ((tok = strtok_r(NULL, " ", &saveptr)) == NULL)
break; /* reached the end of the buffer */
/* encryption is optional */
if (BSTRNCMP(tok, "crypt") == 0) {
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->encrypt_suffix[sizeof(file->encrypt_suffix) - 1] = '\0';
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_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);
file->srvcompprog[sizeof(file->srvcompprog) - 1] = '\0';
if ((tok = strtok_r(NULL, " ", &saveptr)) == NULL)
break;
}
/* "clntcompprog" is optional */
if (BSTRNCMP(tok, "client_custom_compress") == 0) {
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);
file->clntcompprog[sizeof(file->clntcompprog) - 1] = '\0';
if ((tok = strtok_r(NULL, " ", &saveptr)) == NULL)
break;
}
/* "srv_encrypt" is optional */
if (BSTRNCMP(tok, "server_encrypt") == 0) {
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);
file->srv_encrypt[sizeof(file->srv_encrypt) - 1] = '\0';
if ((tok = strtok_r(NULL, " ", &saveptr)) == NULL)
break;
}
/* "clnt_encrypt" is optional */
if (BSTRNCMP(tok, "client_encrypt") == 0) {
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);
file->clnt_encrypt[sizeof(file->clnt_encrypt) - 1] = '\0';
if ((tok = strtok_r(NULL, " ", &saveptr)) == NULL)
break;
}
/* "srv_decrypt_opt" is optional */
if (BSTRNCMP(tok, "server_decrypt_option") == 0) {
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);
file->srv_decrypt_opt[sizeof(file->srv_decrypt_opt) - 1] = '\0';
if ((tok = strtok_r(NULL, " ", &saveptr)) == NULL)
break;
}
/* "clnt_decrypt_opt" is optional */
if (BSTRNCMP(tok, "client_decrypt_option") == 0) {
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);
file->clnt_decrypt_opt[sizeof(file->clnt_decrypt_opt) - 1] = '\0';
if ((tok = strtok_r(NULL, " ", &saveptr)) == NULL)
break;
}
break;
case F_TAPEEND:
tok = strtok_r(NULL, " ", &saveptr);
/* DATE is optional */
if (tok != NULL) {
if (g_str_equal(tok, "DATE")) {
tok = strtok_r(NULL, " ", &saveptr);
if(tok == NULL)
file->datestamp[0] = '\0';
else {
strncpy(file->datestamp, tok, sizeof(file->datestamp) - 1);
file->datestamp[sizeof(file->datestamp) - 1] = '\0';
}
} else {
strange_header(file, buffer, buflen, _("<DATE>"), tok);
}
} else {
file->datestamp[0] = '\0';
}
break;
case F_NOOP:
/* nothing follows */
break;
default:
strange_header(file, buffer, buflen,
_("TAPESTART|DUMPFILE|CONT_DUMPFILE|SPLIT_DUMPFILE|TAPEEND|NOOP"), tok);
goto out;
}
(void)strtok_r(buf, "\n", &saveptr); /* this is the first line */
/* iterate through the rest of the lines */
while ((line = strtok_r(NULL, "\n", &saveptr)) != NULL) {
#define SC "CONT_FILENAME="
if (g_str_has_prefix(line, SC)) {
line += sizeof(SC) - 1;
strncpy(file->cont_filename, line,
sizeof(file->cont_filename) - 1);
file->cont_filename[sizeof(file->cont_filename) - 1] = '\0';
continue;
}
#undef SC
#define SC "PARTIAL="
if (g_str_has_prefix(line, SC)) {
line += sizeof(SC) - 1;
file->is_partial = !strcasecmp(line, "yes");
continue;
}
#undef SC
#define SC "APPLICATION="
if (g_str_has_prefix(line, SC)) {
line += sizeof(SC) - 1;
strncpy(file->application, line,
sizeof(file->application) - 1);
file->application[sizeof(file->application) - 1] = '\0';
continue;
}
#undef SC
#define SC "ORIGSIZE="
if (g_str_has_prefix(line, SC)) {
line += sizeof(SC) - 1;
file->orig_size = OFF_T_ATOI(line);
}
#undef SC
#define SC "NATIVE-CRC="
if (g_str_has_prefix(line, SC)) {
line += sizeof(SC) - 1;
parse_crc(line, &file->native_crc);
}
#undef SC
#define SC "CLIENT-CRC="
if (g_str_has_prefix(line, SC)) {
line += sizeof(SC) - 1;
parse_crc(line, &file->client_crc);
}
#undef SC
#define SC "SERVER-CRC="
if (g_str_has_prefix(line, SC)) {
line += sizeof(SC) - 1;
parse_crc(line, &file->server_crc);
}
#undef SC
#define SC "DLE="
if (g_str_has_prefix(line, SC)) {
line += sizeof(SC) - 1;
amfree(file->dle_str);
file->dle_str = parse_heredoc(line, &saveptr, buffer);
}
#undef SC
#define SC _("To restore, position tape at start of file and run:")
if (g_str_has_prefix(line, SC))
continue;
#undef SC
#define SC "\tdd if=<tape> "
if (g_str_has_prefix(line, SC)) {
char *cmd1, *cmd2, *cmd3=NULL;
/* skip over dd command */
if ((cmd1 = strchr(line, '|')) == NULL) {
strncpy(file->recover_cmd, "BUG\0",
sizeof(file->recover_cmd) - 1);
continue;
}
*cmd1++ = '\0';
/* block out first pipeline command */
if ((cmd2 = strchr(cmd1, '|')) != NULL) {
*cmd2++ = '\0';
if ((cmd3 = strchr(cmd2, '|')) != NULL)
*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
* XXX could be either uncompress or decrypt. Since no
* XXX code actually call uncompress_cmd/decrypt_cmd, it's ok
* XXX for header information.
* one cmds: recover
*/
if ( cmd3 == NULL) {
if (cmd2 == NULL) {
strncpy(file->recover_cmd, cmd1,
sizeof(file->recover_cmd) - 1);
} else {
g_snprintf(file->uncompress_cmd,
sizeof(file->uncompress_cmd), "%s |", cmd1);
strncpy(file->recover_cmd, cmd2,
sizeof(file->recover_cmd) - 1);
}
} else { /* cmd3 presents: decrypt | uncompress | recover */
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);
}
file->recover_cmd[sizeof(file->recover_cmd) - 1] = '\0';
file->uncompress_cmd[sizeof(file->uncompress_cmd) - 1] = '\0';
continue;
}
#undef SC
/* XXX complain about weird lines? */
}
out:
amfree(buf);
amfree(line1);
}
void
dump_dumpfile_t(
const dumpfile_t *file)
{
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);
}
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] || (g_str_equal(suff, "N"))) {
error(_("Invalid encrypt_suffix '%s'\n"), suff);
}
} else {
if (suff[0] && (!g_str_equal(suff, "N"))) {
error(_("Invalid header: encrypt_suffix '%s' specified but not encrypted\n"), suff);
}
}
}
static void
validate_datestamp(
const char *datestamp)
{
if (g_str_equal(datestamp, "X")) {
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:
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:
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 :
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 = g_strdup(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) {
validate_no_space(file->srvcompprog, "srvcompprog");
g_string_append_printf(rval, " server_custom_compress %s",
file->srvcompprog);
} else if (*file->clntcompprog) {
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) {
validate_no_space(file->srv_encrypt, "srv_encrypt");
g_string_append_printf(rval, " server_encrypt %s",
file->srv_encrypt);
} else if (*file->clnt_encrypt) {
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) {
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);
}
g_string_append_printf(rval, "\n");
if (file->cont_filename[0] != '\0') {
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) {
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->native_crc.crc > 0) {
g_string_append_printf(rval, "NATIVE-CRC=%08x:%lld\n",
file->native_crc.crc,
(long long)file->native_crc.size);
}
if (file->client_crc.crc > 0) {
g_string_append_printf(rval, "CLIENT-CRC=%08x:%lld\n",
file->client_crc.crc,
(long long)file->client_crc.size);
}
if (file->server_crc.crc > 0) {
g_string_append_printf(rval, "SERVER-CRC=%08x:%lld\n",
file->server_crc.crc,
(long long)file->server_crc.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:
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:
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.
*/
char *
summarize_header(
const dumpfile_t * file)
{
char *qdisk;
GString *summ;
switch(file->type) {
case F_EMPTY:
return g_strdup(_("EMPTY file"));
case F_UNKNOWN:
return g_strdup(_("UNKNOWN file"));
default:
case F_WEIRD:
return g_strdup(_("WEIRD file"));
case F_TAPESTART:
return g_strdup_printf(_("start of tape: date %s label %s"),
file->datestamp, file->name);
case F_NOOP:
return g_strdup(_("NOOP file"));
case F_DUMPFILE:
case F_CONT_DUMPFILE:
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,
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)
g_string_append_printf(summ, " program %s", file->program);
if (g_str_equal(file->encrypt_suffix, "enc"))
g_string_append_printf(summ, " crypt %s", file->encrypt_suffix);
if (*file->srvcompprog)
g_string_append_printf(summ, " server_custom_compress %s", file->srvcompprog);
if (*file->clntcompprog)
g_string_append_printf(summ, " client_custom_compress %s", file->clntcompprog);
if (*file->srv_encrypt)
g_string_append_printf(summ, " server_encrypt %s", file->srv_encrypt);
if (*file->clnt_encrypt)
g_string_append_printf(summ, " client_encrypt %s", file->clnt_encrypt);
if (*file->srv_decrypt_opt)
g_string_append_printf(summ, " server_decrypt_option %s", file->srv_decrypt_opt);
if (*file->clnt_decrypt_opt)
g_string_append_printf(summ, " client_decrypt_option %s", file->clnt_decrypt_opt);
return g_string_free(summ, FALSE);
case F_TAPEEND:
return g_strdup_printf("end of tape: date %s", file->datestamp);
break;
}
}
int
known_compress_type(
const dumpfile_t * file)
{
if(g_str_equal(file->comp_suffix, ".Z"))
return 1;
#ifdef HAVE_GZIP
if(g_str_equal(file->comp_suffix, ".gz"))
return 1;
#endif
if(g_str_equal(file->comp_suffix, "cust"))
return 1;
return 0;
}
static const struct {
filetype_t type;
const char *str;
} filetypetab[] = {
{ F_UNKNOWN, "UNKNOWN" },
{ F_WEIRD, "WEIRD" },
{ F_TAPESTART, "TAPESTART" },
{ F_TAPEEND, "TAPEEND" },
{ F_DUMPFILE, "FILE" },
{ F_CONT_DUMPFILE, "CONT_FILE" },
{ F_SPLIT_DUMPFILE, "SPLIT_FILE" },
{ F_NOOP, "NOOP" }
};
#define NFILETYPES G_N_ELEMENTS(filetypetab)
static const char *
filetype2str(
filetype_t type)
{
guint i;
for (i = 0; i < NFILETYPES; i++)
if (filetypetab[i].type == type)
return (filetypetab[i].str);
return ("UNKNOWN");
}
static filetype_t
str2filetype(
const char *str)
{
guint i;
for (i = 0; i < NFILETYPES; i++)
if (g_str_equal(filetypetab[i].str, str))
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 (!g_str_equal(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 (!g_str_equal(a->comp_suffix, b->comp_suffix)) return FALSE;
if (!g_str_equal(a->encrypt_suffix, b->encrypt_suffix)) return FALSE;
if (!g_str_equal(a->name, b->name)) return FALSE;
if (!g_str_equal(a->disk, b->disk)) return FALSE;
if (!g_str_equal(a->program, b->program)) return FALSE;
if (!g_str_equal(a->application, b->application)) return FALSE;
if (!g_str_equal(a->srvcompprog, b->srvcompprog)) return FALSE;
if (!g_str_equal(a->clntcompprog, b->clntcompprog)) return FALSE;
if (!g_str_equal(a->srv_encrypt, b->srv_encrypt)) return FALSE;
if (!g_str_equal(a->clnt_encrypt, b->clnt_encrypt)) return FALSE;
if (!g_str_equal(a->recover_cmd, b->recover_cmd)) return FALSE;
if (!g_str_equal(a->uncompress_cmd, b->uncompress_cmd)) return FALSE;
if (!g_str_equal(a->decrypt_cmd, b->decrypt_cmd)) return FALSE;
if (!g_str_equal(a->srv_decrypt_opt, b->srv_decrypt_opt)) return FALSE;
if (!g_str_equal(a->clnt_decrypt_opt, b->clnt_decrypt_opt)) return FALSE;
if (!g_str_equal(a->cont_filename, b->cont_filename)) return FALSE;
if (a->dle_str != b->dle_str && a->dle_str && b->dle_str
&& !g_str_equal(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 = g_strdup(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 = g_strdup(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 = g_strdup(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 && g_str_has_prefix(line,
delimiter)) {
found_delimiter = TRUE;
break;
}
line = c+1;
}
if (!*c) break;
c++;
}
if (!found_delimiter)
break;
g_free(delimiter);
delimiter = g_strdup_printf("%s%d", delimiter_prefix, ++delimiter_n);
delimiter_len = strlen(delimiter);
}
/* we have a delimiter .. now use it */
quoted = g_strdup_printf("<<%s\n%s\n%s", delimiter, text, delimiter);
amfree(delimiter);
return quoted;
}
static char *parse_heredoc(
char *line,
char **saveptr,
const char *message)
{
char *result, *keyword, *new_line;
GPtrArray *array;
gchar **strings;
if (strncmp(line, "<<", 2) != 0)
return g_strdup(line);
array = g_ptr_array_new();
keyword = line + 2;
while ((new_line = strtok_r(NULL, "\n", saveptr)) != NULL) {
if (g_str_equal(new_line, keyword))
break;
g_ptr_array_add(array, g_strdup(new_line));
}
if (!new_line || !g_str_equal(new_line, keyword)) {
g_debug("No end of heredoc: %s", keyword);
if (message)
g_debug("Message: %s", message);
}
g_ptr_array_add(array, NULL);
strings = (gchar **)g_ptr_array_free(array, FALSE);
/*
* This will return an empty string if the only element of the array is a
* NULL pointer - which is what we want
*/
result = g_strjoinv("\n", strings);
g_strfreev(strings);
return result;
}