Implement an optionally persistent queue data structure. This includes a minimal test suite, but does not hook it up to audisp-remote. The queue data structure can keep data only in memory, only on disk (writing it to disk and reading from disk), or in both (writing everything to disk, but reading from disk only data stored in a previous run). audisp-remote will use the last option for performance. The queue file format starts with a fixed header, followed by an array of slots for strings. Due to the fixed size of each slot the file format is rather inefficient, but it is also very simple. The file is preallocated and the string slots will be aligned to a 4KB boundary, so it should be necessary to only write one block to disk when audisp-remote receives a (short) audit record. With the default queue size of 200 items the file will be about 2.4 megabytes large, which is probably not really worth worrying about. If necessary, the space utilization could be improved by storing strings consecutively instead of using pre-arranged slots. The queue file format is intended to be resilient against unexpected termination of the process, and should be resilient against unexpected system crash as long as the OS does not reorder writes (the string data is written before the header that indicates that it is present) - but ultimately resiliency against such failures is limited by other links in the audit record transmission chain - if the record is lost within auditd or audispd, having a resilient queue file format does not help; audit records generated within the kernel are necessarily lost if the system crashes before they are read by auditd because the kernel will not be able to regenerate/retransmit them after the next boot. Index: audit/audisp/plugins/remote/Makefile.am =================================================================== --- audit.orig/audisp/plugins/remote/Makefile.am +++ audit/audisp/plugins/remote/Makefile.am @@ -30,12 +30,16 @@ plugin_conf = au-remote.conf sbin_PROGRAMS = audisp-remote noinst_HEADERS = remote-config.h queue.h man_MANS = audisp-remote.8 audisp-remote.conf.5 +check_PROGRAMS = test-queue +TESTS = $(check_PROGRAMS) audisp_remote_SOURCES = audisp-remote.c remote-config.c queue.c audisp_remote_CFLAGS = -fPIE -DPIE -g -D_REENTRANT -D_GNU_SOURCE -Wundef audisp_remote_LDFLAGS = -pie -Wl,-z,relro $(gss_libs) audisp_remote_LDADD = $(CAPNG_LDADD) +test_queue_SOURCES = queue.c test-queue.c + install-data-hook: mkdir -p -m 0750 ${DESTDIR}${plugin_confdir} $(INSTALL_DATA) -D -m 640 ${srcdir}/$(plugin_conf) ${DESTDIR}${plugin_confdir} Index: audit/audisp/plugins/remote/test-queue.c =================================================================== --- /dev/null +++ audit/audisp/plugins/remote/test-queue.c @@ -0,0 +1,369 @@ +/* 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) +{ + size_t j; + + 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; +} Index: audit/audisp/plugins/remote/queue.c =================================================================== --- audit.orig/audisp/plugins/remote/queue.c +++ audit/audisp/plugins/remote/queue.c @@ -1,4 +1,4 @@ -/* queue.c -- +/* queue.c - a string queue implementation * Copyright 2009, 2011 Red Hat Inc., Durham, North Carolina. * All Rights Reserved. * @@ -21,9 +21,557 @@ */ #include "config.h" +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include "queue.h" +struct queue +{ + int flags; /* Q_* */ + int fd; /* -1 if !Q_IN_FILE */ + /* NULL if !Q_IN_MEMORY. [i] contains a memory copy of the queue entry + "i", if known - it may be NULL even if entry exists. */ + char **memory; + size_t num_entries; + size_t entry_size; + size_t queue_head; + size_t queue_length; + unsigned char buffer[]; /* Used only locally within q_peek() */ +}; + + /* Infrastructure */ + +/* Compile-time expression verification */ +#define verify(E) do { \ + char verify__[(E) ? 1 : -1]; \ + (void)verify__; \ + } while (0) + +/* Like pread(), except that it handles partial reads, and returns 0 on + success. */ +static int full_pread(int fd, void *buf, size_t size, off_t offset) +{ + while (size != 0) { + ssize_t run, res; + + if (size > SSIZE_MAX) + run = SSIZE_MAX; + else + run = size; + res = pread(fd, buf, run, offset); + if (res < 0) + return -1; + if (res == 0) { + errno = ENXIO; /* Any better value? */ + return -1; + } + buf = (unsigned char *)buf + res; + size -= res; + offset += res; + } + return 0; +} + +/* Like pwrite(), except that it handles partial writes, and returns 0 on + success. */ +static int full_pwrite(int fd, const void *buf, size_t size, off_t offset) +{ + while (size != 0) { + ssize_t run, res; + + if (size > SSIZE_MAX) + run = SSIZE_MAX; + else + run = size; + res = pwrite(fd, buf, run, offset); + if (res < 0) + return -1; + if (res == 0) { + errno = ENXIO; /* Any better value? */ + return -1; + } + buf = (const unsigned char *)buf + res; + size -= res; + offset += res; + } + return 0; +} + + /* File format and utilities */ + +/* The mutable part of struct file_header */ +struct fh_state { + uint32_t queue_head; /* 0-based index of the first non-empty entry */ + uint32_t queue_length; /* [0, num_entries] */ +}; + +/* All integer values are in network byte order (big endian) */ +struct file_header +{ + uint8_t magic[14]; /* See fh_magic below */ + uint8_t version; /* File format version, see FH_VERSION* below */ + uint8_t reserved; /* Must be 0 */ + /* Total file size is (num_entries + 1) * entry_size. This must fit + into SIZE_MAX because the "len" parameter of posix_fallocate has + a size_t type. */ + uint32_t num_entries; /* Total number of entries allocated */ + uint32_t entry_size; + struct fh_state s; +}; + +/* Contains a '\0' byte to unambiguously mark the file as a binary file. */ +static const uint8_t fh_magic[14] = "\0audisp-remote"; +#define FH_VERSION_0 0x00 + +/* Return file position for ENTRY in Q */ +static size_t entry_offset (const struct queue *q, size_t entry) +{ + return (entry + 1) * q->entry_size; +} + +/* Synchronize Q if required and return 0. + On error, return -1 and set errno. */ +static int q_sync(struct queue *q) +{ + if ((q->flags & Q_SYNC) == 0) + return 0; + return fdatasync(q->fd); +} + +/* Sync file's fh_state with Q, q_sync (Q), and return 0. + On error, return -1 and set errno. */ +static int sync_fh_state (struct queue *q) +{ + struct fh_state s; + + if (q->fd == -1) + return 0; + + s.queue_head = htonl(q->queue_head); + s.queue_length = htonl(q->queue_length); + if (full_pwrite(q->fd, &s, sizeof(s), offsetof(struct file_header, s)) + != 0) + return -1; + return q_sync(q); +} + + /* Implementation */ + +/* Open PATH for Q, update Q from it, and return 0. + On error, return -1 and set errno; Q->fd may be set even on error. */ +static int q_open_file(struct queue *q, const char *path) +{ + int open_flags, fd_flags; + struct stat st; + struct file_header fh; + + open_flags = O_RDWR; + if ((q->flags & Q_CREAT) != 0) + open_flags |= O_CREAT; + if ((q->flags & Q_EXCL) != 0) + open_flags |= O_EXCL; + q->fd = open(path, open_flags, S_IRUSR | S_IWUSR); + if (q->fd == -1) + return -1; + + fd_flags = fcntl(q->fd, F_GETFD); + if (fd_flags < 0) + return -1; + if (fcntl(q->fd, F_SETFD, fd_flags | FD_CLOEXEC) == -1) + return -1; + + /* File locking in POSIX is pretty much broken... let's hope nobody + attempts to open a single file twice within the same process. + open() above has initialized the file offset to 0, so the lockf() + below affects the whole file. */ + if (lockf(q->fd, F_TLOCK, 0) != 0) { + if (errno == EACCES || errno == EAGAIN) + errno = EBUSY; /* This makes more sense... */ + return -1; + } + + if (fstat(q->fd, &st) != 0) + return -1; + if (st.st_size == 0) { + verify(sizeof(fh.magic) == sizeof(fh_magic)); + memcpy(fh.magic, fh_magic, sizeof(fh.magic)); + fh.version = FH_VERSION_0; + fh.reserved = 0; + fh.num_entries = htonl(q->num_entries); + fh.entry_size = htonl(q->entry_size); + fh.s.queue_head = htonl(0); + fh.s.queue_length = htonl(0); + if (full_pwrite(q->fd, &fh, sizeof(fh), 0) != 0) + return -1; + if (q_sync(q) != 0) + return -1; + if (posix_fallocate(q->fd, 0, + (q->num_entries + 1) * q->entry_size) != 0) + return -1; + } else { + uint32_t file_entries; + if (full_pread(q->fd, &fh, sizeof(fh), 0) != 0) + return -1; + if (memcmp(fh.magic, fh_magic, sizeof(fh.magic)) != 0 + || fh.version != FH_VERSION_0 || fh.reserved != 0 + || fh.entry_size != htonl(q->entry_size)) { + errno = EINVAL; + return -1; + } + file_entries = ntohl(fh.num_entries); + if (file_entries > SIZE_MAX / q->entry_size - 1 + || ((uintmax_t)st.st_size + != (file_entries + 1) * q->entry_size)) { + errno = EINVAL; + return -1; + } + } + /* Note that this may change q->num_entries! */ + q->num_entries = ntohl(fh.num_entries); + q->queue_head = ntohl(fh.s.queue_head); + q->queue_length = ntohl(fh.s.queue_length); + if (q->queue_head >= q->num_entries + || q->queue_length > q->num_entries) { + errno = EINVAL; + return -1; + } + return 0; +} + +/* Like q_open(), but does not handle Q_RESIZE, and NUM_ENTRIES is only used + when creating a new file. */ +static struct queue *q_open_no_resize(int q_flags, const char *path, + size_t num_entries, size_t entry_size) +{ + struct queue *q; + int saved_errno; + + if ((q_flags & (Q_IN_MEMORY | Q_IN_FILE)) == 0) { + errno = EINVAL; + return NULL; + } + if (num_entries == 0 || num_entries > UINT32_MAX + || entry_size < 1 /* for trailing NUL */ + || entry_size < sizeof(struct file_header) /* for Q_IN_FILE */ + /* to allocate "struct queue" including its buffer*/ + || entry_size > UINT32_MAX - sizeof(struct queue)) { + errno = EINVAL; + return NULL; + } + if (entry_size > SIZE_MAX + || num_entries > SIZE_MAX / entry_size - 1 /* for Q_IN_FILE */ + || num_entries > SIZE_MAX / sizeof(*q->memory)) { + errno = EINVAL; + return NULL; + } + + q = malloc(sizeof(*q) + entry_size); + if (q == NULL) + return NULL; + q->flags = q_flags; + q->fd = -1; + q->memory = NULL; + q->num_entries = num_entries; + q->entry_size = entry_size; + q->queue_head = 0; + q->queue_length = 0; + + if ((q_flags & Q_IN_MEMORY) != 0) { + size_t i; + + q->memory = malloc(num_entries * sizeof(*q->memory)); + if (q->memory == NULL) + goto err; + for (i = 0; i < num_entries; i++) + q->memory[i] = NULL; + } + + if ((q_flags & Q_IN_FILE) != 0 && q_open_file(q, path) != 0) + goto err; + + return q; + +err: + saved_errno = errno; + if (q->fd != -1) + close(q->fd); + free(q->memory); + free(q); + errno = saved_errno; + return NULL; +} + +void q_close(struct queue *q) +{ + if (q->fd != -1) + close(q->fd); /* Also releases the file lock */ + if (q->memory != NULL) { + size_t i; + + for (i = 0; i < q->num_entries; i++) + free(q->memory[i]); + free(q->memory); + } + free(q); +} + +/* Internal use only: add DATA to Q, but don't update fh_state. */ +static int q_append_no_sync_fh_state(struct queue *q, const char *data) +{ + size_t data_size, entry_index; + char *copy; + + if (q->queue_length == q->num_entries) { + errno = ENOSPC; + return -1; + } + + data_size = strlen(data) + 1; + if (data_size > q->entry_size) { + errno = EINVAL; + return -1; + } + + entry_index = (q->queue_head + q->queue_length) % q->num_entries; + if (q->memory != NULL) { + if (q->memory[entry_index] != NULL) { + errno = EIO; /* This is _really_ unexpected. */ + return -1; + } + copy = malloc(data_size); + if (copy == NULL) + return -1; + memcpy(copy, data, data_size); + } else + copy = NULL; + + if (q->fd != -1) { + size_t offset; + + offset = entry_offset(q, entry_index); + if (full_pwrite(q->fd, data, data_size, offset) != 0) { + int saved_errno; + + saved_errno = errno; + if (copy != NULL) + free(copy); + errno = saved_errno; + return -1; + } + } + + if (copy != NULL) + q->memory[entry_index] = copy; + + q->queue_length++; + + return 0; +} + +int q_append(struct queue *q, const char *data) +{ + int r; + + r = q_append_no_sync_fh_state(q, data); + if (r != 0) + return r; + + return sync_fh_state(q); /* Calls q_sync() */ +} + +int q_peek(struct queue *q, char *buf, size_t size) +{ + const unsigned char *data; + size_t data_size; + + if (q->queue_length == 0) + return 0; + + if (q->memory != NULL && q->memory[q->queue_head] != NULL) { + data = q->memory[q->queue_head]; + data_size = strlen(data) + 1; + } else if (q->fd != -1) { + const unsigned char *end; + + if (full_pread(q->fd, q->buffer, q->entry_size, + entry_offset(q, q->queue_head)) != 0) + return -1; + data = q->buffer; + end = memchr(q->buffer, '\0', q->entry_size); + if (end == NULL) { + /* FIXME: silently drop this entry? */ + errno = EBADMSG; + return -1; + } + data_size = (end - data) + 1; + + if (q->memory != NULL) { + char *copy; + + copy = malloc(data_size); + if (copy != NULL) { /* Silently ignore failures. */ + memcpy(copy, data, data_size); + q->memory[q->queue_head] = copy; + } + } + } else { + errno = EIO; /* This is _really_ unexpected. */ + return -1; + } + + if (size < data_size) { + errno = ERANGE; + return -1; + } + memcpy(buf, data, data_size); + return 1; +} + +/* Internal use only: drop head of Q, but don't write this into the file */ +static int q_drop_head_memory_only(struct queue *q) +{ + if (q->queue_length == 0) { + errno = EINVAL; + return -1; + } + + if (q->memory != NULL) { + free(q->memory[q->queue_head]); + q->memory[q->queue_head] = NULL; + } + + q->queue_head++; + if (q->queue_head == q->num_entries) + q->queue_head = 0; + q->queue_length--; + return 0; +} + +int q_drop_head(struct queue *q) +{ + int r; + + r = q_drop_head_memory_only(q); + if (r != 0) + return r; + + return sync_fh_state(q); /* Calls q_sync() */ +} + +size_t q_queue_length(struct queue *q) +{ + return q->queue_length; +} + +struct queue *q_open(int q_flags, const char *path, size_t num_entries, + size_t entry_size) +{ + struct queue *q, *q2; + char *tmp_path, *buf; + size_t path_len; + int saved_errno, fd; + + q = q_open_no_resize(q_flags, path, num_entries, entry_size); + if (q == NULL || q->num_entries == num_entries) + return q; + + if ((q->flags & Q_RESIZE) == 0) { + saved_errno = EINVAL; + goto err_errno_q; + } + + if (q->queue_length > num_entries) { + saved_errno = ENOSPC; + goto err_errno_q; + } + + buf = malloc(entry_size); + if (buf == NULL) { + saved_errno = errno; + goto err_errno_q; + } + + path_len = strlen(path); + tmp_path = malloc(path_len + 7); + if (tmp_path == NULL) { + saved_errno = errno; + goto err_errno_buf; + } + memcpy(tmp_path, path, path_len); + memcpy(tmp_path + path_len, "XXXXXX", 7); + /* We really want tmpnam() here (safe due to the Q_EXCL below), but gcc + warns on any use of tmpnam(). */ + fd = mkstemp(tmp_path); + if (fd == -1) { + saved_errno = errno; + goto err_errno_tmp_path; + } + if (close(fd) != 0 || unlink(tmp_path) != 0) { + saved_errno = errno; + goto err_errno_tmp_file; + } + + q2 = q_open_no_resize(q_flags | Q_CREAT | Q_EXCL, tmp_path, num_entries, + entry_size); + if (q2 == NULL) { + saved_errno = errno; + goto err_errno_tmp_file; + } + if (q2->num_entries != num_entries) { + errno = EIO; /* This is _really_ unexpected. */ + goto err_q2; + } + + for (;;) { + int r; + + r = q_peek(q, buf, entry_size); + if (r == 0) + break; + if (r != 1) + goto err_q2; + + if (q_append_no_sync_fh_state(q2, buf) != 0) + goto err_q2; + if (q_drop_head_memory_only(q) != 0) + goto err_q2; + } + if (sync_fh_state(q2) != 0) + goto err_q2; + + if (rename(tmp_path, path) != 0) + goto err_q2; + + q_close(q); + free(buf); + free(tmp_path); + return q2; + +err_q2: + saved_errno = errno; + q_close(q2); +err_errno_tmp_file: + unlink(tmp_path); +err_errno_tmp_path: + free(tmp_path); +err_errno_buf: + free(buf); +err_errno_q: + q_close(q); + errno = saved_errno; + return NULL; +} + + /* The old interface */ + static volatile event_t **q; static unsigned int q_next, q_last, q_depth; Index: audit/audisp/plugins/remote/queue.h =================================================================== --- audit.orig/audisp/plugins/remote/queue.h +++ audit/audisp/plugins/remote/queue.h @@ -1,5 +1,5 @@ -/* queue.h -- - * Copyright 2009 Red Hat Inc., Durham, North Carolina. +/* queue.h -- a queue abstraction + * Copyright 2009, 2011 Red Hat Inc., Durham, North Carolina. * All Rights Reserved. * * This library is free software; you can redistribute it and/or @@ -18,11 +18,13 @@ * * Authors: * Steve Grubb + * Miloslav Trmač */ #ifndef QUEUE_HEADER #define QUEUE_HEADER +#include #include "libaudit.h" typedef struct event @@ -37,5 +39,52 @@ event_t *dequeue(int peek); int queue_length(void); void destroy_queue(void); + /* The new interface */ + +struct queue; + +enum { + /* Queue storage. Both options can be set at the same time. */ + Q_IN_MEMORY = 1 << 0, /* Keep a copy of the queue in memory */ + Q_IN_FILE = 1 << 1, /* Store the queue in a file */ + /* Other flags */ + /* With Q_IN_FILE, create the queue if it does not exist */ + Q_CREAT = 1 << 2, + Q_EXCL = 1 << 3, /* With Q_CREAT, don't open an existing queue */ + Q_SYNC = 1 << 4, /* With Q_IN_FILE, fdatasync() after each operation */ + /* With Q_IN_FILE, resize the queue length if necessary */ + Q_RESIZE = 1 << 5, +}; + +/* Open a queue using Q_FLAGS and return it. + + If Q_IN_FILE, use PATH for the file. + If Q_IN_FILE, NUM_ENTRIES must be the same for all users of the file unless + Q_RESIZE is set. + ENTRY_SIZE is the maximum length of a stored string, including the trailing + NUL. If Q_IN_FILE, it must be the same for all users of the file. + + On error, return NULL and set errno. + + Note that the returned queue may not be concurrently accessed by more than + one thread. */ +struct queue *q_open(int q_flags, const char *path, size_t num_entries, + size_t entry_size); +/* Close Q. */ +void q_close(struct queue *q); + +/* Add DATA to tail of Q and return 0. + On error, return -1 and set errno. */ +int q_append(struct queue *q, const char *data); +/* Peek at head of Q, storing it into BUF of SIZE. + Return 1 if an entry exists, 0 if queue is empty. + On error, return -1 and set errno. */ +int q_peek(struct queue *q, char *buf, size_t size); +/* Drop head of Q and return 0. + On error, return -1 and set errno. */ +int q_drop_head(struct queue *q); +/* Return the number of entires in Q. */ +size_t q_queue_length(struct queue *q); + #endif