/* test-queue.c -- test suite for persistent-queue.c * Copyright 2011 Red Hat Inc., Durham, North Carolina. * All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Authors: * Miloslav Trmač */ #include "config.h" #include #include #include #include #include #include #include #include #include "queue.h" #define NUM_ENTRIES 7 /* 3*4096, larger than MAX_AUDIT_MESSAGE_LENGTH. The same value is used in the main audisp-remote code. */ #define ENTRY_SIZE 12288 static char filename[] = "/tmp/tqXXXXXX"; static struct queue *q; static char *sample_entries[NUM_ENTRIES - 1]; #define NUM_SAMPLE_ENTRIES (sizeof(sample_entries) / sizeof(*sample_entries)) #define die(...) die__(__LINE__, __VA_ARGS__) static void __attribute__((format (printf, 2, 3))) die__(int line, const char *message, ...) { va_list ap; fprintf(stderr, "test-queue: %d: ", line); va_start(ap, message); vfprintf(stderr, message, ap); va_end(ap); putc('\n', stderr); abort(); } #define err(...) err__(__LINE__, __VA_ARGS__) static void __attribute__((format (printf, 2, 3))) err__(int line, const char *message, ...) { char *errno_str; va_list ap; errno_str = strerror(errno); fprintf(stderr, "test-queue: %d: ", line); va_start(ap, message); vfprintf(stderr, message, ap); va_end(ap); fprintf(stderr, ": %s\n", errno_str); abort(); } static void init_sample_entries(void) { size_t i; for (i = 0; i < NUM_SAMPLE_ENTRIES; i++) { char *e; size_t j, len; len = rand() % ENTRY_SIZE; e = malloc(len + 1); if (e == NULL) err("malloc"); for (j = 0; j < len; j++) e[j] = rand() % CHAR_MAX + 1; e[j] = '\0'; sample_entries[i] = e; } } static void free_sample_entries(void) { size_t i; for (i = 0; i < NUM_SAMPLE_ENTRIES; i++) free(sample_entries[i]); } static void test_q_open(void) { struct queue *q2; /* Test that flags are honored */ q2 = q_open(Q_IN_FILE | Q_CREAT | Q_EXCL, filename, NUM_ENTRIES, ENTRY_SIZE); if (q2 != NULL) die("q_open didn't fail"); if (errno != EEXIST) err("q_open"); /* Test that locking is enforced. Use a separate process because fcntl()/lockf() locking is attached to processes, not file descriptors. */ fflush(NULL); switch (fork()) { case -1: err("fork"); case 0: q2 = q_open(Q_IN_FILE, filename, NUM_ENTRIES, ENTRY_SIZE); if (q2 != NULL) die("q_open didn't fail"); if (errno != EBUSY) err("q_open"); _exit(0); default: { int status; if (wait(&status) == (pid_t)-1) err("wait"); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) die("wait status %d", status); } } } static void test_empty_q (void) { char buf[ENTRY_SIZE]; if (q_peek(q, buf, sizeof(buf)) != 0) die("q_peek reports non-empty"); if (q_drop_head(q) != -1) die("q_drop_head didn't fail"); if (errno != EINVAL) err("q_drop_head"); if (q_queue_length(q) != 0) die("Unexpected q_queue_length"); } static void test_basic_data (void) { char buf[ENTRY_SIZE + 1]; int i; if (q_append(q, " ") != 0) die("q_append"); memset (buf, 'A', ENTRY_SIZE); buf[ENTRY_SIZE] = '\0'; if (q_append(q, buf) != -1) die("q_append didn't fail"); if (errno != EINVAL) err("q_append"); buf[ENTRY_SIZE - 1] = '\0'; if (q_append(q, buf) != 0) die("q_append"); if (q_queue_length(q) != 2) die("Unexpected q_queue_length"); if (q_peek(q, buf, sizeof(buf)) < 1) err("q_peek"); if (strcmp(buf, " ") != 0) die("invalid data returned"); if (q_drop_head(q) != 0) err("q_drop_head"); if (q_peek(q, buf, ENTRY_SIZE - 1) != -1) err("q_peek didn't fail"); if (errno != ERANGE) err("q_peek"); for (i = 0; i < 2; i++) { size_t j; if (q_peek(q, buf, sizeof(buf)) < 1) err("q_peek"); for (j = 0; j < ENTRY_SIZE - 1; j++) { if (buf[j] != 'A') die("invalid data at %zu", j); } if (buf[j] != '\0') die("invalid data at %zu", j); } if (q_drop_head(q) != 0) err("q_drop_head"); if (q_queue_length(q) != 0) die("Unexpected q_queue_length"); } static void append_sample_entries(size_t count) { size_t i; for (i = 0; i < count; i++) { if (q_append(q, sample_entries[i % NUM_SAMPLE_ENTRIES]) != 0) die("q_append %zu", i); } } static void verify_sample_entries(size_t count) { char buf[ENTRY_SIZE + 1]; size_t i; if (q_queue_length(q) != count) die("Unexpected q_queue_length"); for (i = 0; i < count; i++) { if (q_peek(q, buf, sizeof(buf)) < 1) err("q_peek %zu", i); if (strcmp(buf, sample_entries[i % NUM_SAMPLE_ENTRIES]) != 0) die("invalid data %zu", i); if (q_drop_head(q) != 0) err("q_drop_head"); } if (q_peek(q, buf, sizeof(buf)) != 0) die("q_peek reports non-empty"); } static void test_run(int flags) { size_t j; q = q_open(flags | Q_CREAT | Q_EXCL, filename, NUM_ENTRIES, ENTRY_SIZE); if (q == NULL) err("q_open"); if ((flags & Q_IN_FILE) != 0) test_q_open(); /* Do this enough times to get a wraparound */ for (j = 0; j < NUM_ENTRIES; j++) { test_empty_q(); test_basic_data(); } append_sample_entries(NUM_ENTRIES - 1); if (q_queue_length(q) != NUM_ENTRIES - 1) die("Unexpected q_queue_length"); q_close(q); q = q_open(flags, filename, NUM_ENTRIES, ENTRY_SIZE); if (q == NULL) err("q_open"); if ((flags & Q_IN_FILE) != 0) /* Test that the queue can be reopened and data has been preserved. */ verify_sample_entries(NUM_ENTRIES - 1); else /* Test that a new in-memory queue is empty. */ verify_sample_entries(0); q_close(q); if ((flags & Q_IN_FILE) != 0 && unlink(filename) != 0) err("unlink"); } static void test_resizing(void) { q = q_open(Q_IN_FILE | Q_CREAT | Q_EXCL, filename, NUM_ENTRIES, ENTRY_SIZE); if (q == NULL) err("q_open"); append_sample_entries(NUM_ENTRIES); if (q_queue_length(q) != NUM_ENTRIES) die("Unexpected q_queue_length"); q_close(q); /* Verify num_entries is validated */ q = q_open(Q_IN_FILE, filename, NUM_ENTRIES + 1, ENTRY_SIZE); if (q != NULL) die("q_open didn't fail"); if (errno != EINVAL) err("q_open"); q = q_open(Q_IN_FILE, filename, NUM_ENTRIES - 1, ENTRY_SIZE); if (q != NULL) die("q_open didn't fail"); if (errno != EINVAL) err("q_open"); /* Test increasing size */ q = q_open(Q_IN_FILE | Q_RESIZE, filename, 2 * NUM_ENTRIES, ENTRY_SIZE); if (q == NULL) err("q_open"); verify_sample_entries(NUM_ENTRIES); append_sample_entries(NUM_ENTRIES); q_close(q); /* Test decreasing size */ q = q_open(Q_IN_FILE | Q_RESIZE, filename, NUM_ENTRIES / 2, ENTRY_SIZE); if (q != NULL) die("q_open didn't fail"); if (errno != ENOSPC) err("q_open"); q = q_open(Q_IN_FILE | Q_RESIZE, filename, NUM_ENTRIES, ENTRY_SIZE); if (q == NULL) err("q_open"); verify_sample_entries(NUM_ENTRIES); q_close(q); if (unlink(filename) != 0) err("unlink"); } int main(void) { static const int flags[] = { Q_IN_MEMORY, Q_IN_FILE, Q_IN_FILE | Q_SYNC, Q_IN_MEMORY | Q_IN_FILE }; int fd; size_t i; init_sample_entries(); /* We really want tmpnam() here (safe due to the Q_EXCL below), but gcc warns on any use of tmpnam(). */ fd = mkstemp(filename); if (fd == -1) err("tmpnam"); if (close(fd) != 0) err("close"); if (unlink(filename) != 0) err("unlink"); for (i = 0; i < sizeof(flags) / sizeof(*flags); i++) test_run(flags[i]); test_resizing(); free_sample_entries(); return EXIT_SUCCESS; }