/*
* Amanda, The Advanced Maryland Automatic Network Disk Archiver
* Copyright (c) 1991-1998 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.
*/
#include "amanda.h"
#include "amar.h"
#include "testutils.h"
#include "simpleprng.h"
static char *temp_filename = NULL;
/****
* Macros for creating files with a particular structure
*/
#define WRITE_HEADER(fd, version) do { \
char hdr[28]; \
bzero(hdr, 28); \
snprintf(hdr, 28, "AMANDA ARCHIVE FORMAT %d", (version)); \
g_assert(full_write((fd), hdr, 28) == 28); \
} while(0);
#define WRITE_RECORD(fd, filenum, attrid, size, eoa, data) do { \
struct { uint16_t f; uint16_t a; uint32_t s; } rec; \
rec.f = htons((filenum)); \
rec.a = htons((attrid)); \
rec.s = htonl((size) | (eoa? 0x80000000 : 0)); \
g_assert(full_write((fd), &rec, sizeof(rec)) == sizeof(rec)); \
g_assert(full_write((fd), (data), (size)) == (size)); \
} while(0);
#define WRITE_RECORD_STR(fd, filenum, attrid, eoa, str) do { \
size_t len = strlen((str)); \
WRITE_RECORD((fd), (filenum), (attrid), len, (eoa), (str)); \
} while(0);
/****
* Assertions for amanda_read_archive callbacks
*/
typedef enum {
EXP_END,
EXP_START_FILE,
EXP_ATTRDATA,
EXP_FINISH_FILE,
} expected_kind_t;
typedef struct {
expected_kind_t kind;
uint16_t filenum;
uint16_t attrid;
char *data;
size_t datasize;
gboolean multipart_ok;
gboolean eoa;
gboolean truncated;
gboolean should_ignore;
gboolean isstr;
} expected_step_t;
typedef struct {
expected_step_t *steps;
int curstep;
} expected_state_t;
#define EXPECT_START_FILE(filenum, data, datasize, should_ignore) \
{ EXP_START_FILE, (filenum), 0, (data), (datasize), 0, 0, 0, (should_ignore), 0 }
#define EXPECT_START_FILE_STR(filenum, filename, should_ignore) \
{ EXP_START_FILE, (filenum), 0, (filename), strlen((filename)), 0, 0, 0, (should_ignore), 1 }
#define EXPECT_ATTR_DATA(filenum, attrid, data, datasize, eoa, truncated) \
{ EXP_ATTRDATA, (filenum), (attrid), (data), (datasize), 0, (eoa), (truncated), 0, 0 }
#define EXPECT_ATTR_DATA_MULTIPART(filenum, attrid, data, datasize, eoa, truncated) \
{ EXP_ATTRDATA, (filenum), (attrid), (data), (datasize), 1, (eoa), (truncated), 0, 0 }
#define EXPECT_ATTR_DATA_STR(filenum, attrid, datastr, eoa, truncated) \
{ EXP_ATTRDATA, (filenum), (attrid), (datastr), strlen((datastr)), 0, (eoa), (truncated), 0, 1 }
#define EXPECT_FINISH_FILE(filenum, truncated) \
{ EXP_FINISH_FILE, (filenum), 0, 0, 0, 0, 0, (truncated), 0, 0 }
#define EXPECT_END() \
{ EXP_END, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
#define EXPECT_FAILURE(fmt, ...) do { \
fprintf(stderr, fmt "\n", __VA_ARGS__); \
exit(1); \
} while(0)
static gboolean
file_start_cb(
gpointer user_data,
uint16_t filenum,
gpointer filename,
gsize filename_len,
gboolean *ignore,
gpointer *file_data G_GNUC_UNUSED)
{
expected_state_t *state = user_data;
expected_step_t *step = state->steps + state->curstep;
tu_dbg("file_start_cb(NULL, %d, '%s', %zd, .., ..)\n",
(int)filenum, (char *)filename, filename_len);
if (step->kind != EXP_START_FILE)
EXPECT_FAILURE("step %d: unexpected new file with fileid %d",
state->curstep, (int)filenum);
if (step->filenum != filenum)
EXPECT_FAILURE("step %d: expected new file with filenum %d; got filenum %d",
state->curstep, (int)step->filenum, (int)filenum);
if (filename_len != step->datasize)
EXPECT_FAILURE("step %d: filename lengths do not match: got %zd, expected %zd",
state->curstep, filename_len, step->datasize);
if (memcmp(filename, step->data, filename_len)) {
if (step->isstr) {
EXPECT_FAILURE("step %d: new file's filename does not match: got '%*s', expected '%*s'",
state->curstep, (int)filename_len, (char *)filename,
(int)step->datasize, (char *)step->data);
} else {
EXPECT_FAILURE("step %d: new file's filename does not match",
state->curstep);
}
}
*ignore = step->should_ignore;
state->curstep++;
return TRUE;
}
static gboolean
file_finish_cb(
gpointer user_data,
uint16_t filenum,
gpointer *file_data G_GNUC_UNUSED,
gboolean truncated)
{
expected_state_t *state = user_data;
expected_step_t *step = state->steps + state->curstep;
tu_dbg("file_finish_cb(NULL, %d, NULL, %d)\n",
(int)filenum, truncated);
if (step->kind != EXP_FINISH_FILE)
EXPECT_FAILURE("step %d: unexpected file finish with fileid %d",
state->curstep, (int)filenum);
if (step->truncated && !truncated)
EXPECT_FAILURE("step %d: file %d was unexpectedly not truncated",
state->curstep, (int)filenum);
if (step->truncated && !truncated)
EXPECT_FAILURE("step %d: file %d was unexpectedly truncated",
state->curstep, (int)filenum);
state->curstep++;
return TRUE;
}
static gboolean
frag_cb(
gpointer user_data,
uint16_t filenum,
gpointer file_data G_GNUC_UNUSED,
uint16_t attrid,
gpointer attrid_data G_GNUC_UNUSED,
gpointer *attr_data G_GNUC_UNUSED,
gpointer data,
gsize datasize,
gboolean eoa,
gboolean truncated)
{
expected_state_t *state = user_data;
expected_step_t *step = state->steps + state->curstep;
tu_dbg("file_finish_cb(NULL, %d, NULL, %d, %p, %zd, %d, %d)\n",
(int)filenum, (int)attrid, data, datasize, eoa, truncated);
if (step->kind != EXP_ATTRDATA)
EXPECT_FAILURE("step %d: unexpected attribute data with fileid %d, attrid %d",
state->curstep, (int)filenum, (int)attrid);
if (step->filenum != filenum)
EXPECT_FAILURE("step %d: expected attribute data with filenum %d; got filenum %d",
state->curstep, (int)step->filenum, (int)filenum);
if (step->attrid != attrid)
EXPECT_FAILURE("step %d: expected attribute data with attrid %d; got attrid %d",
state->curstep, (int)step->attrid, (int)attrid);
/* if we're accepting multiple fragments of the attribute here (due to internal
* buffering by the reader), then handle that specially */
if (step->multipart_ok && datasize < step->datasize) {
if (eoa)
EXPECT_FAILURE("step %d: file %d attribute %d: early EOA in multipart attribute",
state->curstep, (int)filenum, (int)attrid);
if (memcmp(data, step->data, datasize)) {
EXPECT_FAILURE("step %d: attribute's data does not match",
state->curstep);
}
step->data += datasize;
step->datasize -= datasize;
return TRUE;
}
if (step->eoa && !eoa)
EXPECT_FAILURE("step %d: file %d attribute %d: expected EOA did not appear",
state->curstep, (int)filenum, (int)attrid);
if (!step->eoa && eoa)
EXPECT_FAILURE("step %d: file %d attribute %d: unexpected EOA",
state->curstep, (int)filenum, (int)attrid);
if (!step->truncated && truncated)
EXPECT_FAILURE("step %d: file %d attribute %d was unexpectedly truncated",
state->curstep, (int)filenum, (int)attrid);
if (step->truncated && !truncated)
EXPECT_FAILURE("step %d: file %d attribute %d was unexpectedly not truncated",
state->curstep, (int)filenum, (int)attrid);
if (datasize != step->datasize)
EXPECT_FAILURE("step %d: file %d attribute %d lengths do not match: "
"got %zd, expected %zd",
state->curstep, (int)filenum, (int)attrid,
datasize, step->datasize);
if (memcmp(data, step->data, datasize)) {
if (step->isstr) {
EXPECT_FAILURE("step %d: attribute's data does not match: got '%*s', expected '%*s'",
state->curstep, (int)datasize, (char *)data,
(int)step->datasize, (char *)step->data);
} else {
EXPECT_FAILURE("step %d: attribute's data does not match",
state->curstep);
}
}
state->curstep++;
return TRUE;
}
/****
* Utilities
*/
static int
open_temp(gboolean write)
{
int fd = open(temp_filename, write? O_WRONLY|O_CREAT|O_TRUNC : O_RDONLY, 0777);
if (fd < 0) {
perror("open temporary file");
exit(1);
}
return fd;
}
static void
check_gerror_(
gboolean ok,
GError *error,
const char *fn)
{
if (ok && !error)
return;
if (ok)
EXPECT_FAILURE(
"'%s' set 'error' but did not indicate an error condition: %s (%s)\n",
fn, error->message, strerror(error->code));
else if (!error)
EXPECT_FAILURE(
"'%s' indicated an error condition but did not set 'error'.\n", fn);
else
EXPECT_FAILURE(
"'%s' error: %s (%s)\n", fn, error->message, strerror(error->code));
exit(1);
}
#define check_gerror(ok, error, fn) check_gerror_((ok)!=0, (error), (fn))
static void
check_gerror_matches_(
gboolean ok,
GError *error,
const char *matches,
const char *fn)
{
if (!ok && error) {
if (!g_str_equal(matches, error->message)) {
EXPECT_FAILURE(
"%s produced error '%s' but expected '%s'\n",
fn, error->message, matches);
exit(1);
}
return;
}
if (ok)
EXPECT_FAILURE(
"'%s' correctly set 'error' but did not indicate an error condition: %s (%s)\n",
fn, error->message, strerror(error->code));
else /* (!error) */
EXPECT_FAILURE(
"'%s' correctly indicated an error condition but did not set 'error'.\n", fn);
exit(1);
}
#define check_gerror_matches(ok, error, match, fn) \
check_gerror_matches_((ok)!=0, (error), (match), (fn))
static void
try_reading_fd(
expected_step_t *steps,
amar_attr_handling_t *handling,
int fd)
{
amar_t *ar;
expected_state_t state = { steps, 0 };
GError *error = NULL;
gboolean ok;
ar = amar_new(fd, O_RDONLY, &error);
check_gerror(ar, error, "amar_new");
ok = amar_read(ar, &state, handling, file_start_cb, file_finish_cb, NULL, &error);
if (ok || error)
check_gerror(ok, error, "amar_read");
if (steps[state.curstep].kind != EXP_END)
EXPECT_FAILURE("Stopped reading early at step %d", state.curstep);
ok = amar_close(ar, &error);
check_gerror(ok, error, "amar_close");
}
static void
try_reading(
expected_step_t *steps,
amar_attr_handling_t *handling)
{
int fd;
fd = open_temp(0);
try_reading_fd(steps, handling, fd);
close(fd);
}
static void
try_reading_with_error(
expected_step_t *steps,
amar_attr_handling_t *handling,
const char *message)
{
amar_t *ar;
expected_state_t state = { steps, 0 };
int fd;
GError *error = NULL;
gboolean ok;
fd = open_temp(0);
ar = amar_new(fd, O_RDONLY, &error);
check_gerror(ar, error, "amar_new");
ok = amar_read(ar, &state, handling, file_start_cb, file_finish_cb, NULL, &error);
check_gerror_matches(ok, error, message, "amar_read");
amar_close(ar, NULL);
close(fd);
}
/****
* Test various valid inputs
*/
static int
test_simple_read(void)
{
int fd;
fd = open_temp(1);
WRITE_HEADER(fd, 1);
WRITE_RECORD_STR(fd, 1, AMAR_ATTR_FILENAME, 1, "/first/filename");
WRITE_RECORD_STR(fd, 1, 18, 1, "eighteen");
WRITE_HEADER(fd, 1);
WRITE_RECORD_STR(fd, 1, 19, 0, "nine");
WRITE_RECORD_STR(fd, 1, 20, 0, "twen");
WRITE_RECORD_STR(fd, 1, 19, 1, "teen");
WRITE_RECORD_STR(fd, 1, 20, 1, "ty");
WRITE_RECORD_STR(fd, 1, AMAR_ATTR_EOF, 1, "");
close(fd);
{
amar_attr_handling_t handling[] = {
{ 19, 256, frag_cb, NULL }, /* reassemble this attribute */
{ 20, 0, frag_cb, NULL }, /* but pass along each fragment of this */
{ 0, 256, frag_cb, NULL },
};
expected_step_t steps[] = {
EXPECT_START_FILE_STR(1, "/first/filename", 0),
EXPECT_ATTR_DATA_STR(1, 18, "eighteen", 1, 0),
EXPECT_ATTR_DATA_STR(1, 20, "twen", 0, 0),
EXPECT_ATTR_DATA_STR(1, 19, "nineteen", 1, 0),
EXPECT_ATTR_DATA_STR(1, 20, "ty", 1, 0),
EXPECT_FINISH_FILE(1, 0),
EXPECT_END(),
};
try_reading(steps, handling);
}
return 1;
}
static int
test_read_buffering(void)
{
int fd;
fd = open_temp(1);
WRITE_HEADER(fd, 1);
WRITE_RECORD_STR(fd, 1, AMAR_ATTR_FILENAME, 1, "file1");
WRITE_HEADER(fd, 1);
WRITE_RECORD_STR(fd, 2, AMAR_ATTR_FILENAME, 1, "file2");
WRITE_RECORD_STR(fd, 2, 19, 0, "1"); /* one byte at a time, for 12 bytes */
WRITE_RECORD_STR(fd, 2, 19, 0, "9");
WRITE_RECORD_STR(fd, 2, 21, 1, "012345678901234567890123456789"); /* thirty bytes exactly */
WRITE_RECORD_STR(fd, 2, 19, 0, "1");
WRITE_RECORD_STR(fd, 1, 18, 0, "ATTR");
WRITE_RECORD_STR(fd, 2, 19, 0, "9");
WRITE_RECORD_STR(fd, 2, 19, 0, "1");
WRITE_RECORD_STR(fd, 2, 19, 0, "9");
WRITE_HEADER(fd, 1);
WRITE_RECORD_STR(fd, 1, 20, 0, "TWENTYTWE"); /* nine bytes, then three in the next frag */
WRITE_RECORD_STR(fd, 2, 19, 0, "1");
WRITE_RECORD_STR(fd, 1, 20, 1, "NTY");
WRITE_RECORD_STR(fd, 2, 19, 0, "9");
WRITE_RECORD_STR(fd, 1, 18, 0, "181818"); /* hit ten bytes exactly */
WRITE_RECORD_STR(fd, 2, 19, 0, "1");
WRITE_RECORD_STR(fd, 2, 19, 0, "9");
WRITE_RECORD_STR(fd, 1, 18, 0, "ATTR");
WRITE_RECORD_STR(fd, 1, 22, 0, "012345678"); /* nine bytes followed by 20 */
WRITE_RECORD_STR(fd, 1, 18, 1, "18");
WRITE_RECORD_STR(fd, 1, 22, 1, "01234567890123456789");
WRITE_RECORD_STR(fd, 2, 19, 0, "1");
WRITE_RECORD_STR(fd, 1, AMAR_ATTR_EOF, 1, "");
WRITE_RECORD_STR(fd, 2, 19, 1, "9");
WRITE_RECORD_STR(fd, 2, AMAR_ATTR_EOF, 1, "");
close(fd);
{
amar_attr_handling_t handling[] = {
{ 0, 10, frag_cb, NULL }, /* reassemble all fragments in 10-byte chunks */
};
expected_step_t steps[] = {
EXPECT_START_FILE_STR(1, "file1", 0),
EXPECT_START_FILE_STR(2, "file2", 0),
EXPECT_ATTR_DATA_STR(2, 21, "012345678901234567890123456789", 1, 0),
EXPECT_ATTR_DATA_STR(1, 20, "TWENTYTWENTY", 1, 0),
EXPECT_ATTR_DATA_STR(1, 18, "ATTR181818", 0, 0),
EXPECT_ATTR_DATA_STR(2, 19, "1919191919", 0, 0),
EXPECT_ATTR_DATA_STR(1, 18, "ATTR18", 1, 0),
EXPECT_ATTR_DATA_STR(1, 22, "01234567801234567890123456789", 1, 0),
EXPECT_FINISH_FILE(1, 0),
EXPECT_ATTR_DATA_STR(2, 19, "19", 1, 0),
EXPECT_FINISH_FILE(2, 0),
EXPECT_END(),
};
try_reading(steps, handling);
}
return 1;
}
static int
test_missing_eoa(void)
{
int fd;
fd = open_temp(1);
WRITE_HEADER(fd, 1);
WRITE_RECORD_STR(fd, 1, AMAR_ATTR_FILENAME, 1, "file1");
WRITE_RECORD_STR(fd, 1, 21, 0, "attribu"); /* note no EOA */
WRITE_RECORD_STR(fd, 1, AMAR_ATTR_EOF, 1, "");
close(fd);
{
amar_attr_handling_t handling[] = {
{ 0, 1024, frag_cb, NULL },
};
expected_step_t steps[] = {
EXPECT_START_FILE_STR(1, "file1", 0),
EXPECT_ATTR_DATA_STR(1, 21, "attribu", 1, 1),
EXPECT_FINISH_FILE(1, 0),
EXPECT_END(),
};
try_reading(steps, handling);
}
return 1;
}
static int
test_ignore(void)
{
int fd;
fd = open_temp(1);
WRITE_HEADER(fd, 1);
WRITE_RECORD_STR(fd, 1, AMAR_ATTR_FILENAME, 1, "file1");
WRITE_HEADER(fd, 1);
WRITE_RECORD_STR(fd, 2, AMAR_ATTR_FILENAME, 1, "file2");
WRITE_RECORD_STR(fd, 2, 20, 1, "attr20");
WRITE_RECORD_STR(fd, 1, 21, 0, "attr");
WRITE_RECORD_STR(fd, 1, 21, 1, "21");
WRITE_HEADER(fd, 1);
WRITE_RECORD_STR(fd, 3, AMAR_ATTR_FILENAME, 1, "file3");
WRITE_HEADER(fd, 1);
WRITE_RECORD_STR(fd, 4, AMAR_ATTR_FILENAME, 1, "file4");
WRITE_RECORD_STR(fd, 3, 22, 1, "attr22");
WRITE_RECORD_STR(fd, 4, 23, 1, "attr23");
WRITE_RECORD_STR(fd, 4, AMAR_ATTR_EOF, 1, "");
WRITE_RECORD_STR(fd, 3, AMAR_ATTR_EOF, 1, "");
WRITE_RECORD_STR(fd, 2, AMAR_ATTR_EOF, 1, "");
WRITE_RECORD_STR(fd, 1, AMAR_ATTR_EOF, 1, "");
close(fd);
{
amar_attr_handling_t handling[] = {
{ 0, 10, frag_cb, NULL }, /* reassemble all fragments in 10-byte chunks */
};
expected_step_t steps[] = {
EXPECT_START_FILE_STR(1, "file1", 1),
EXPECT_START_FILE_STR(2, "file2", 0),
EXPECT_ATTR_DATA_STR(2, 20, "attr20", 1, 0),
EXPECT_START_FILE_STR(3, "file3", 1),
EXPECT_START_FILE_STR(4, "file4", 0),
EXPECT_ATTR_DATA_STR(4, 23, "attr23", 1, 0),
EXPECT_FINISH_FILE(4, 0),
EXPECT_FINISH_FILE(2, 0),
EXPECT_END(),
};
try_reading(steps, handling);
}
return 1;
}
static int
test_missing_eof(void)
{
int fd;
fd = open_temp(1);
WRITE_HEADER(fd, 1);
WRITE_RECORD_STR(fd, 1, AMAR_ATTR_FILENAME, 1, "file!");
WRITE_RECORD_STR(fd, 1, 20, 1, "attribute");
WRITE_RECORD_STR(fd, 1, 21, 0, "attribu"); /* note no EOA */
close(fd);
{
amar_attr_handling_t handling[] = {
{ 0, 1024, frag_cb, NULL },
};
expected_step_t steps[] = {
EXPECT_START_FILE_STR(1, "file!", 0),
EXPECT_ATTR_DATA_STR(1, 20, "attribute", 1, 0),
EXPECT_ATTR_DATA_STR(1, 21, "attribu", 1, 1),
EXPECT_FINISH_FILE(1, 0),
EXPECT_END(),
};
try_reading(steps, handling);
}
return 1;
}
static int
test_extra_records(void)
{
int fd;
fd = open_temp(1);
WRITE_HEADER(fd, 1);
WRITE_RECORD_STR(fd, 4, AMAR_ATTR_EOF, 1, "");
WRITE_RECORD_STR(fd, 5, 20, 1, "old attribute");
WRITE_RECORD_STR(fd, 6, AMAR_ATTR_FILENAME, 1, "file!");
WRITE_RECORD_STR(fd, 6, 21, 0, "attribu"); /* note no EOA */
WRITE_RECORD_STR(fd, 5, AMAR_ATTR_EOF, 1, "");
WRITE_RECORD_STR(fd, 6, 21, 1, "te");
WRITE_RECORD_STR(fd, 6, AMAR_ATTR_EOF, 1, "");
close(fd);
{
amar_attr_handling_t handling[] = {
{ 0, 1024, frag_cb, NULL },
};
expected_step_t steps[] = {
EXPECT_START_FILE_STR(6, "file!", 0),
EXPECT_ATTR_DATA_STR(6, 21, "attribute", 1, 0),
EXPECT_FINISH_FILE(6, 0),
EXPECT_END(),
};
try_reading(steps, handling);
}
return 1;
}
static gboolean
early_exit_frag_cb(
gpointer user_data G_GNUC_UNUSED,
uint16_t filenum G_GNUC_UNUSED,
gpointer file_data G_GNUC_UNUSED,
uint16_t attrid G_GNUC_UNUSED,
gpointer attrid_data G_GNUC_UNUSED,
gpointer *attr_data G_GNUC_UNUSED,
gpointer data G_GNUC_UNUSED,
gsize datasize G_GNUC_UNUSED,
gboolean eoa G_GNUC_UNUSED,
gboolean truncated G_GNUC_UNUSED)
{
return FALSE;
}
static int
test_early_exit(void)
{
int fd;
fd = open_temp(1);
WRITE_HEADER(fd, 1);
WRITE_RECORD_STR(fd, 6, AMAR_ATTR_FILENAME, 1, "file!");
WRITE_RECORD_STR(fd, 6, 21, 1, "attribu");
WRITE_RECORD_STR(fd, 6, AMAR_ATTR_EOF, 1, "");
close(fd);
{
amar_attr_handling_t handling[] = {
{ 0, 0, early_exit_frag_cb, NULL },
};
expected_step_t steps[] = {
EXPECT_START_FILE_STR(6, "file!", 0),
EXPECT_FINISH_FILE(6, 1),
EXPECT_END(),
};
try_reading(steps, handling);
}
return 1;
}
/****
* Test the write side, using round trips.
*/
/* just try to execute most of the writing code */
static int
test_writing_coverage(void)
{
int fd, fd2;
off_t posn, fdsize;
char buf[16300];
char buf2[16300];
char *bigbuf;
size_t bigbuf_size = 1024*50+93;
simpleprng_state_t prng;
gsize i;
guint16 attrid = 20;
amar_t *arch = NULL;
amar_file_t *af = NULL, *af2 = NULL;
amar_attr_t *at = NULL, *at2 = NULL;
GError *error = NULL;
gboolean ok;
/* set up some data buffers */
for (i = 0; i < sizeof(buf); i++) {
buf[i] = 0xfe;
buf2[i] = 0xaa;
}
bigbuf = g_malloc(bigbuf_size);
simpleprng_seed(&prng, 0xfeaa);
simpleprng_fill_buffer(&prng, bigbuf, bigbuf_size);
fd = open("amar-test.big", O_CREAT|O_WRONLY|O_TRUNC, 0777);
g_assert(fd >= 0);
g_assert(full_write(fd, bigbuf, bigbuf_size) == bigbuf_size);
close(fd);
fd = open_temp(1);
arch = amar_new(fd, O_WRONLY, &error);
check_gerror(arch, error, "amar_new");
g_assert(arch != NULL);
af = amar_new_file(arch, "MyFile", 0, &posn, &error);
check_gerror(af, error, "amar_new_file");
tu_dbg("MyFile starts at 0x%x\n", (int)posn)
g_assert(af != NULL);
/* by character with EOA */
at = amar_new_attr(af, attrid++, &error);
check_gerror(at, error, "amar_new_attr");
g_assert(at != NULL);
ok = amar_attr_add_data_buffer(at, buf, sizeof(buf), 1, &error);
check_gerror(ok, error, "amar_attr_add_data_buffer");
ok = amar_attr_close(at, &error);
check_gerror(ok, error, "amar_attr_close");
/* by character without EOA */
at = amar_new_attr(af, attrid++, &error);
check_gerror(at, error, "amar_new_attr");
g_assert(at != NULL);
ok = amar_attr_add_data_buffer(at, buf2, sizeof(buf2), 0, &error);
check_gerror(ok, error, "amar_attr_add_data_buffer");
ok = amar_attr_close(at, &error);
check_gerror(ok, error, "amar_attr_close");
/* open up a new file, for fun */
af2 = amar_new_file(arch, "MyOtherFile", 0, &posn, &error);
check_gerror(af2, error, "amar_new_file");
tu_dbg("MyOtherFile starts at 0x%x\n", (int)posn)
/* by file descriptor, to the first file */
at = amar_new_attr(af, attrid++, &error);
check_gerror(at, error, "amar_new_attr");
fd2 = open("amar-test.big", O_RDONLY);
g_assert(fd2 >= 0);
fdsize = amar_attr_add_data_fd(at, fd2, 0, &error);
check_gerror(fdsize != -1, error, "amar_attr_add_data_fd");
g_assert(fdsize > 0);
close(fd2);
unlink("amar-test.big");
ok = amar_attr_close(at, &error);
check_gerror(ok, error, "amar_attr_close");
ok = amar_file_close(af, &error);
check_gerror(ok, error, "amar_file_close");
/* interlaeave two attributes */
at = amar_new_attr(af2, attrid++, &error);
check_gerror(at, error, "amar_new_attr");
at2 = amar_new_attr(af2, attrid++, &error);
check_gerror(at2, error, "amar_new_attr");
ok = amar_attr_add_data_buffer(at, buf, 72, 0, &error);
check_gerror(ok, error, "amar_attr_add_data_buffer");
ok = amar_attr_add_data_buffer(at2, buf2, 72, 0, &error);
check_gerror(ok, error, "amar_attr_add_data_buffer");
ok = amar_attr_add_data_buffer(at, buf, 13, 0, &error);
check_gerror(ok, error, "amar_attr_add_data_buffer");
ok = amar_attr_add_data_buffer(at2, buf2, 13, 1, &error);
check_gerror(ok, error, "amar_attr_add_data_buffer");
ok = amar_attr_close(at, &error);
check_gerror(ok, error, "amar_attr_close");
ok = amar_attr_close(at2, &error);
check_gerror(ok, error, "amar_attr_close");
ok = amar_file_close(af2, &error);
check_gerror(ok, error, "amar_file_close");
ok = amar_close(arch, &error);
check_gerror(ok, error, "amar_close");
close(fd);
{
amar_attr_handling_t handling[] = {
{ 22, bigbuf_size+1, frag_cb, NULL }, /* buffer the big attr */
{ 0, 0, frag_cb, NULL }, /* don't buffer other records */
};
expected_step_t steps[] = {
EXPECT_START_FILE_STR(1, "MyFile", 0),
EXPECT_ATTR_DATA_MULTIPART(1, 20, buf, sizeof(buf), 1, 0),
EXPECT_ATTR_DATA_MULTIPART(1, 21, buf2, sizeof(buf2), 0, 0),
EXPECT_ATTR_DATA_MULTIPART(1, 21, buf2, 0, 1, 0), /* trailing EOA */
EXPECT_START_FILE_STR(2, "MyOtherFile", 0),
EXPECT_ATTR_DATA(1, 22, bigbuf, bigbuf_size, 1, 0),
EXPECT_FINISH_FILE(1, 0),
EXPECT_ATTR_DATA_MULTIPART(2, 23, buf, 72, 0, 0),
EXPECT_ATTR_DATA_MULTIPART(2, 24, buf2, 72, 0, 0),
EXPECT_ATTR_DATA_MULTIPART(2, 23, buf+72, 13, 0, 0),
EXPECT_ATTR_DATA_MULTIPART(2, 24, buf2+72, 13, 1, 0),
EXPECT_ATTR_DATA_MULTIPART(2, 23, buf, 0, 1, 0),
EXPECT_FINISH_FILE(2, 0),
EXPECT_END(),
};
try_reading(steps, handling);
}
return 1;
}
/* test big attributes */
static int
test_big_attr(void)
{
int fd, fd2;
off_t fdsize;
char *bigbuf;
const size_t max_record_data_size = 4*1024*1024;
size_t bigbuf_size = max_record_data_size + 1274; /* a record and a bit */
simpleprng_state_t prng;
guint16 attrid = 20;
amar_t *arch = NULL;
amar_file_t *af = NULL;
amar_attr_t *at = NULL;
GError *error = NULL;
gboolean ok;
/* set up some data buffers */
bigbuf = g_malloc(bigbuf_size);
simpleprng_seed(&prng, 0xb001);
simpleprng_fill_buffer(&prng, bigbuf, bigbuf_size);
fd = open("amar-test.big", O_CREAT|O_WRONLY|O_TRUNC, 0777);
g_assert(fd >= 0);
g_assert(full_write(fd, bigbuf, bigbuf_size) == bigbuf_size);
close(fd);
fd = open_temp(1);
arch = amar_new(fd, O_WRONLY, &error);
check_gerror(arch, error, "amar_new");
af = amar_new_file(arch, "bigstuff", 0, NULL, &error);
check_gerror(af, error, "amar_new_file");
/* by character */
at = amar_new_attr(af, attrid++, &error);
check_gerror(at, error, "amar_new_attr");
ok = amar_attr_add_data_buffer(at, bigbuf, bigbuf_size, 1, &error);
check_gerror(ok, error, "amar_attr_add_data_buffer");
ok = amar_attr_close(at, &error);
check_gerror(ok, error, "amar_attr_close");
/* by file descriptor */
at = amar_new_attr(af, attrid++, &error);
check_gerror(at, error, "amar_new_attr");
fd2 = open("amar-test.big", O_RDONLY);
g_assert(fd2 >= 0);
fdsize = amar_attr_add_data_fd(at, fd2, 1, &error);
check_gerror(fdsize != -1, error, "amar_attr_add_data_fd");
g_assert(fdsize > 0);
close(fd2);
unlink("amar-test.big");
ok = amar_attr_close(at, &error);
check_gerror(ok, error, "amar_attr_close");
ok = amar_file_close(af, &error);
check_gerror(ok, error, "amar_file_close");
ok = amar_close(arch, &error);
check_gerror(ok, error, "amar_close");
close(fd);
{
amar_attr_handling_t handling[] = {
{ 0, 0, frag_cb, NULL }, /* don't buffer records */
};
expected_step_t steps[] = {
EXPECT_START_FILE_STR(1, "bigstuff", 0),
EXPECT_ATTR_DATA_MULTIPART(1, 20, bigbuf, max_record_data_size, 0, 0),
EXPECT_ATTR_DATA_MULTIPART(1, 20, bigbuf+max_record_data_size, bigbuf_size-max_record_data_size, 1, 0),
EXPECT_ATTR_DATA_MULTIPART(1, 21, bigbuf, max_record_data_size, 0, 0),
EXPECT_ATTR_DATA_MULTIPART(1, 21, bigbuf+max_record_data_size, bigbuf_size-max_record_data_size, 1, 0),
EXPECT_FINISH_FILE(1, 0),
EXPECT_END(),
};
try_reading(steps, handling);
}
return 1;
}
/* like test_big_attr, but using a pipe and ignoring one of the attrs in hopes
* of triggering an lseek(), which will fail on a pipe. */
static int
test_pipe(void)
{
int fd;
int p[2];
off_t fdsize;
char *bigbuf;
const size_t max_record_data_size = 4*1024*1024;
size_t bigbuf_size = max_record_data_size + 1274; /* a record and a bit */
simpleprng_state_t prng;
guint16 attrid = 20;
amar_t *arch = NULL;
amar_file_t *af = NULL;
amar_attr_t *at = NULL;
GError *error = NULL;
gboolean ok;
/* set up some data buffers */
bigbuf = g_malloc(bigbuf_size);
simpleprng_seed(&prng, 0xb001);
simpleprng_fill_buffer(&prng, bigbuf, bigbuf_size);
fd = open("amar-test.big", O_CREAT|O_WRONLY|O_TRUNC, 0777);
g_assert(fd >= 0);
g_assert(full_write(fd, bigbuf, bigbuf_size) == bigbuf_size);
close(fd);
g_assert(pipe(p) >= 0);
switch (fork()) {
case 0: /* child */
close(p[0]);
arch = amar_new(p[1], O_WRONLY, &error);
check_gerror(arch, error, "amar_new");
g_assert(arch != NULL);
af = amar_new_file(arch, "bigstuff", 0, NULL, &error);
check_gerror(af, error, "amar_new_file");
/* by character */
at = amar_new_attr(af, attrid++, &error);
check_gerror(at, error, "amar_new_attr");
ok = amar_attr_add_data_buffer(at, bigbuf, bigbuf_size, 1, &error);
check_gerror(ok, error, "amar_attr_add_data_buffer");
ok = amar_attr_close(at, &error);
check_gerror(ok, error, "amar_attr_close");
/* by file descriptor */
at = amar_new_attr(af, attrid++, &error);
check_gerror(at, error, "amar_new_attr");
fd = open("amar-test.big", O_RDONLY);
g_assert(fd >= 0);
fdsize = amar_attr_add_data_fd(at, fd, 1, &error);
check_gerror(fdsize != -1, error, "amar_attr_add_data_fd");
g_assert(fdsize > 0);
close(fd);
unlink("amar-test.big");
ok = amar_attr_close(at, &error);
check_gerror(ok, error, "amar_attr_close");
ok = amar_file_close(af, &error);
check_gerror(ok, error, "amar_file_close");
ok = amar_close(arch, &error);
check_gerror(ok, error, "amar_close");
close(p[1]);
exit(0);
case -1:
perror("fork");
exit(1);
default: { /* parent */
amar_attr_handling_t handling[] = {
{ 20, 0, NULL, NULL }, /* ignore attr 20 */
{ 0, 0, frag_cb, NULL }, /* don't buffer records */
};
expected_step_t steps[] = {
EXPECT_START_FILE_STR(1, "bigstuff", 0),
EXPECT_ATTR_DATA_MULTIPART(1, 21, bigbuf, max_record_data_size, 0, 0),
EXPECT_ATTR_DATA_MULTIPART(1, 21, bigbuf+max_record_data_size, bigbuf_size-max_record_data_size, 1, 0),
EXPECT_FINISH_FILE(1, 0),
EXPECT_END(),
};
int status;
close(p[1]);
try_reading_fd(steps, handling, p[0]);
close(p[0]);
(void)wait(&status);
if(WIFSIGNALED(status)) {
printf("child was terminated by signal %d\n", WTERMSIG(status));
exit(1);
}
}
}
return 1;
}
/****
* Invalid inputs - test error returns
*/
static int
test_no_header(void)
{
int fd;
fd = open_temp(1);
WRITE_RECORD_STR(fd, 1, AMAR_ATTR_FILENAME, 1, "/first/filename");
close(fd);
{
amar_attr_handling_t handling[] = {
{ 0, 0, frag_cb, NULL },
};
expected_step_t steps[] = {
EXPECT_END(),
};
try_reading_with_error(steps, handling,
"Archive read does not begin at a header record, position = 0");
}
return 1;
}
static int
test_invalid_eof(void)
{
int fd;
fd = open_temp(1);
WRITE_HEADER(fd, 1);
WRITE_RECORD_STR(fd, 1, AMAR_ATTR_FILENAME, 1, "hi");
WRITE_RECORD_STR(fd, 1, AMAR_ATTR_EOF, 1, "abc");
close(fd);
{
amar_attr_handling_t handling[] = {
{ 0, 0, frag_cb, NULL },
};
expected_step_t steps[] = {
EXPECT_START_FILE_STR(1, "hi", 0),
EXPECT_END(),
};
try_reading_with_error(steps, handling,
"Archive contains an EOF record with nonzero size, position = 46");
}
return 1;
}
static int
test_header_vers(void)
{
int fd;
char hdr[32];
bzero(hdr, sizeof(hdr));
strcpy(hdr, "AMANDA ARCHIVE FORMAT 2");
fd = open_temp(1);
if (full_write(fd, hdr, sizeof(hdr)) < sizeof(hdr)) {
perror("full_write");
exit(1);
}
close(fd);
{
amar_attr_handling_t handling[] = {
{ 0, 0, frag_cb, NULL },
};
expected_step_t steps[] = {
EXPECT_END(),
};
try_reading_with_error(steps, handling,
"Archive version 2 is not supported, position = 0");
}
return 1;
}
/****
* Driver
*/
int
main(int argc, char **argv)
{
int rv;
char *cwd = g_get_current_dir();
static TestUtilsTest tests[] = {
TU_TEST(test_simple_read, 90),
TU_TEST(test_read_buffering, 90),
TU_TEST(test_missing_eoa, 90),
TU_TEST(test_ignore, 90),
TU_TEST(test_missing_eof, 90),
TU_TEST(test_extra_records, 90),
TU_TEST(test_early_exit, 90),
TU_TEST(test_writing_coverage, 90),
TU_TEST(test_big_attr, 90),
TU_TEST(test_pipe, 90),
TU_TEST(test_no_header, 90),
TU_TEST(test_invalid_eof, 90),
TU_TEST(test_header_vers, 90),
TU_END()
};
temp_filename = g_strjoin(NULL, cwd, "/amar-test.tmp", NULL);
rv = testutils_run_tests(argc, argv, tests);
unlink(temp_filename);
return rv;
}