aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/audit/audisp/plugins/remote/queue.c
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/audit/audisp/plugins/remote/queue.c')
-rw-r--r--framework/src/audit/audisp/plugins/remote/queue.c574
1 files changed, 574 insertions, 0 deletions
diff --git a/framework/src/audit/audisp/plugins/remote/queue.c b/framework/src/audit/audisp/plugins/remote/queue.c
new file mode 100644
index 00000000..971e4e46
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/remote/queue.c
@@ -0,0 +1,574 @@
+/* queue.c - a string queue implementation
+ * Copyright 2009, 2011 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Authors:
+ * Steve Grubb <sgrubb@redhat.com>
+ * Miloslav Trmač <mitr@redhat.com>
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#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. */
+ unsigned 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);
+}
+
+/* Queue 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;
+#ifdef HAVE_POSIX_FALLOCATE
+ if (posix_fallocate(q->fd, 0,
+ (q->num_entries + 1) * q->entry_size) != 0)
+ return -1;
+#endif
+ } 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 sz = num_entries * sizeof(*q->memory);
+
+ q->memory = malloc(sz);
+ if (q->memory == NULL)
+ goto err;
+ memset(q->memory, 0, sz);
+ }
+
+ 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;
+ unsigned 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((char *)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) {
+ unsigned 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 data_size;
+}
+
+/* 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(const 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 < 0)
+ 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;
+}