Add a virRotatingFile object which allows writing to a file
with automation rotation to N backup files when a size limit
is reached. This is useful for guest logging when a guaranteed
finite size limit is required. Use of external tools like
logrotate is inadequate since it leaves the possibility for
guest to DOS the host in between invokations of logrotate.
Signed-off-by: Daniel P. Berrange <berrange(a)redhat.com>
---
po/POTFILES.in | 1 +
src/Makefile.am | 1 +
src/libvirt_private.syms | 6 +
src/util/virrotatingfile.c | 237 +++++++++++++++++++++++++
src/util/virrotatingfile.h | 44 +++++
tests/Makefile.am | 6 +
tests/virrotatingfiletest.c | 415 ++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 710 insertions(+)
create mode 100644 src/util/virrotatingfile.c
create mode 100644 src/util/virrotatingfile.h
create mode 100644 tests/virrotatingfiletest.c
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0cc5b99..401ac6f 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -214,6 +214,7 @@ src/util/virpolkit.c
src/util/virportallocator.c
src/util/virprocess.c
src/util/virrandom.c
+src/util/virrotatingfile.c
src/util/virsexpr.c
src/util/virscsi.c
src/util/virsocketaddr.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 99b4993..ee082ec 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -150,6 +150,7 @@ UTIL_SOURCES = \
util/virprobe.h \
util/virprocess.c util/virprocess.h \
util/virrandom.h util/virrandom.c \
+ util/virrotatingfile.h util/virrotatingfile.c \
util/virscsi.c util/virscsi.h \
util/virseclabel.c util/virseclabel.h \
util/virsexpr.c util/virsexpr.h \
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index a835f18..ff4b0e4 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -2046,6 +2046,12 @@ virRandomGenerateWWN;
virRandomInt;
+# util/virrotatingfile.h
+virRotatingFileNew;
+virRotatingFileWrite;
+virRotatingFileFree;
+
+
# util/virscsi.h
virSCSIDeviceFileIterate;
virSCSIDeviceFree;
diff --git a/src/util/virrotatingfile.c b/src/util/virrotatingfile.c
new file mode 100644
index 0000000..7af9a32
--- /dev/null
+++ b/src/util/virrotatingfile.c
@@ -0,0 +1,237 @@
+/*
+ * virrotatingfile.c: file I/O with size rotation
+ *
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * 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, see
+ * <
http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <config.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "virrotatingfile.h"
+#include "viralloc.h"
+#include "virerror.h"
+#include "virstring.h"
+#include "virfile.h"
+#include "virlog.h"
+
+VIR_LOG_INIT("util.rotatingfile");
+
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+struct virRotatingFile {
+ char *path;
+ int fd;
+ size_t maxbackup;
+ off_t curlen;
+ off_t maxlen;
+ mode_t mode;
+};
+
+
+static int virRotatingFileOpen(virRotatingFilePtr file)
+{
+ VIR_DEBUG("Opening %s", file->path);
+ if ((file->fd = open(file->path,
+ O_WRONLY|O_CREAT|O_APPEND, file->mode)) < 0) {
+ virReportSystemError(errno,
+ _("Unable to open file: %s"),
+ file->path);
+ goto error;
+ }
+
+ file->curlen = lseek(file->fd, 0, SEEK_END);
+ if (file->curlen == (off_t)-1) {
+ virReportSystemError(errno,
+ _("Unable to determine current file offset: %s"),
+ file->path);
+ goto error;
+ }
+
+ return 0;
+
+ error:
+ VIR_FORCE_CLOSE(file->fd);
+ return -1;
+}
+
+
+static int virRotatingFileDelete(virRotatingFilePtr file)
+{
+ size_t i;
+
+ if (unlink(file->path) < 0 &&
+ errno != ENOENT) {
+ virReportSystemError(errno,
+ _("Unable to delete file %s"),
+ file->path);
+ return -1;
+ }
+
+ for (i = 0; i < file->maxbackup; i++) {
+ char *oldpath;
+ if (virAsprintf(&oldpath, "%s.%zu", file->path, i) < 0)
+ return -1;
+
+ if (unlink(oldpath) < 0 &&
+ errno != ENOENT) {
+ virReportSystemError(errno,
+ _("Unable to delete file %s"),
+ oldpath);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+
+virRotatingFilePtr virRotatingFileNew(const char *path,
+ off_t maxlen,
+ size_t maxbackup,
+ bool truncate,
+ mode_t mode)
+{
+ virRotatingFilePtr file;
+
+
+ if (VIR_ALLOC(file) < 0)
+ goto error;
+
+ if (VIR_STRDUP(file->path, path) < 0)
+ goto error;
+
+ file->mode = mode;
+ file->maxbackup = maxbackup;
+ file->maxlen = maxlen;
+
+ if (truncate &&
+ virRotatingFileDelete(file) < 0)
+ goto error;
+
+ if (virRotatingFileOpen(file) < 0)
+ goto error;
+
+ return file;
+
+ error:
+ virRotatingFileFree(file);
+ return NULL;
+}
+
+
+const char *virRotatingFileGetPath(virRotatingFilePtr file)
+{
+ return file->path;
+}
+
+
+static int virRotatingFileRollover(virRotatingFilePtr file)
+{
+ size_t i;
+ char *nextpath;
+ char *thispath;
+ int ret = -1;
+
+ VIR_DEBUG("Rollover %s", file->path);
+ if (virAsprintf(&nextpath, "%s.%zu", file->path, file->maxbackup
- 1) < 0)
+ return -1;
+
+ for (i = file->maxbackup; i > 0; i--) {
+ if (i == 1) {
+ if (VIR_STRDUP(thispath, file->path) < 0)
+ goto cleanup;
+ } else {
+ if (virAsprintf(&thispath, "%s.%zu", file->path, i - 2) <
0)
+ goto cleanup;
+ }
+ VIR_DEBUG("Rollover %s -> %s", thispath, nextpath);
+
+ if (rename(thispath, nextpath) < 0 &&
+ errno != ENOENT) {
+ virReportSystemError(errno,
+ _("Unable to rename %s to %s"),
+ thispath, nextpath);
+ goto cleanup;
+ }
+
+ VIR_FREE(nextpath);
+ nextpath = thispath;
+ thispath = NULL;
+ }
+
+ VIR_DEBUG("Rollover done %s", file->path);
+
+ ret = 0;
+ cleanup:
+ VIR_FREE(nextpath);
+ VIR_FREE(thispath);
+ return ret;
+}
+
+ssize_t virRotatingFileWrite(virRotatingFilePtr file,
+ const char *buf,
+ size_t len)
+{
+ ssize_t ret = 0;
+ while (len) {
+ size_t towrite = len;
+
+ if ((file->curlen + towrite) > file->maxlen)
+ towrite = file->maxlen - file->curlen;
+
+ if (towrite) {
+ if (safewrite(file->fd, buf, towrite) != towrite) {
+ virReportSystemError(errno,
+ _("Unable to write to file %s"),
+ file->path);
+ return -1;
+ }
+
+ len -= towrite;
+ buf += towrite;
+ ret += towrite;
+ file->curlen += towrite;
+ }
+
+ if (file->curlen == file->maxlen && len) {
+ VIR_DEBUG("Hit max size %zu on %s\n", file->maxlen,
file->path);
+ VIR_FORCE_CLOSE(file->fd);
+
+ if (virRotatingFileRollover(file) < 0)
+ return -1;
+
+ if (virRotatingFileOpen(file) < 0)
+ return -1;
+ }
+ }
+
+ return ret;
+}
+
+
+void virRotatingFileFree(virRotatingFilePtr file)
+{
+ if (!file)
+ return;
+
+ VIR_FORCE_CLOSE(file->fd);
+ VIR_FREE(file->path);
+ VIR_FREE(file);
+}
diff --git a/src/util/virrotatingfile.h b/src/util/virrotatingfile.h
new file mode 100644
index 0000000..f55d485
--- /dev/null
+++ b/src/util/virrotatingfile.h
@@ -0,0 +1,44 @@
+/*
+ * virrotatingfile.h: writing to auto-rotating files
+ *
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * 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, see
+ * <
http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __VIR_ROTATING_FILE_H__
+# define __VIR_ROTATING_FILE_H__
+
+# include "internal.h"
+
+typedef struct virRotatingFile virRotatingFile;
+typedef virRotatingFile *virRotatingFilePtr;
+
+virRotatingFilePtr virRotatingFileNew(const char *path,
+ off_t maxlen,
+ size_t maxbackup,
+ bool truncate,
+ mode_t mode);
+
+const char *virRotatingFileGetPath(virRotatingFilePtr file);
+
+ssize_t virRotatingFileWrite(virRotatingFilePtr file,
+ const char *buf,
+ size_t len);
+
+void virRotatingFileFree(virRotatingFilePtr file);
+
+#endif /* __VIR_ROTATING_FILE_H__ */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 4af38fe..73d551b 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -173,6 +173,7 @@ test_programs = virshtest sockettest \
virkeycodetest \
virlockspacetest \
virlogtest \
+ virrotatingfiletest \
virstringtest \
virportallocatortest \
sysinfotest \
@@ -1106,6 +1107,11 @@ virpolkittest_SOURCES = \
virpolkittest_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
virpolkittest_LDADD = $(LDADDS) $(DBUS_LIBS)
+virrotatingfiletest_SOURCES = \
+ virrotatingfiletest.c testutils.h testutils.c
+virrotatingfiletest_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
+virrotatingfiletest_LDADD = $(LDADDS) $(DBUS_LIBS)
+
virsystemdtest_SOURCES = \
virsystemdtest.c testutils.h testutils.c
virsystemdtest_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
diff --git a/tests/virrotatingfiletest.c b/tests/virrotatingfiletest.c
new file mode 100644
index 0000000..692f8c6
--- /dev/null
+++ b/tests/virrotatingfiletest.c
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * 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, see
+ * <
http://www.gnu.org/licenses/>.
+ *
+ * Author: Daniel P. Berrange <berrange(a)redhat.com>
+ */
+
+#include <config.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "virrotatingfile.h"
+#include "virlog.h"
+#include "testutils.h"
+
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+VIR_LOG_INIT("tests.rotatingfiletest");
+
+#define FILENAME "virrotatingfiledata.txt"
+#define FILENAME0 "virrotatingfiledata.txt.0"
+#define FILENAME1 "virrotatingfiledata.txt.1"
+
+static int testRotatingFileAssertOne(const char *filename,
+ off_t size)
+{
+ struct stat sb;
+
+ if (stat(filename, &sb) < 0) {
+ if (size == (off_t)-1) {
+ return 0;
+ } else {
+ fprintf(stderr, "File %s does not exist\n", filename);
+ return -1;
+ }
+ } else {
+ if (size == (off_t)-1) {
+ fprintf(stderr, "File %s should not exist\n", filename);
+ return -1;
+ } else if (sb.st_size != size) {
+ fprintf(stderr, "File %s should be %zu bytes\n", filename, size);
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+}
+
+static int testRotatingFileAssertFiles(off_t baseSize,
+ off_t backup0Size,
+ off_t backup1Size)
+{
+ if (testRotatingFileAssertOne(FILENAME, baseSize) < 0 ||
+ testRotatingFileAssertOne(FILENAME0, backup0Size) < 0 ||
+ testRotatingFileAssertOne(FILENAME1, backup1Size) < 0)
+ return -1;
+ return 0;
+}
+
+static int testRotatingFileInitOne(const char *filename,
+ off_t size)
+{
+ if (size == (off_t)-1) {
+ VIR_DEBUG("Deleting %s", filename);
+ unlink(filename);
+ } else {
+ VIR_DEBUG("Creating %s size %zu", filename, (size_t)size);
+ char buf[1024];
+ int fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0700);
+ if (fd < 0) {
+ fprintf(stderr, "Cannot create %s\n", filename);
+ return -1;
+ }
+ memset(buf, 0x5e, sizeof(buf));
+ while (size) {
+ size_t towrite = size;
+ if (towrite > sizeof(buf))
+ towrite = sizeof(buf);
+
+ if (safewrite(fd, buf, towrite) != towrite) {
+ fprintf(stderr, "Cannot write to %s\n", filename);
+ VIR_FORCE_CLOSE(fd);
+ return -1;
+ }
+ size -= towrite;
+ }
+ VIR_FORCE_CLOSE(fd);
+ }
+ return 0;
+}
+
+static int testRotatingFileInitFiles(off_t baseSize,
+ off_t backup0Size,
+ off_t backup1Size)
+{
+ if (testRotatingFileInitOne(FILENAME, baseSize) < 0 ||
+ testRotatingFileInitOne(FILENAME0, backup0Size) < 0 ||
+ testRotatingFileInitOne(FILENAME1, backup1Size) < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+static int testRotatingFileNew(const void *data ATTRIBUTE_UNUSED)
+{
+ virRotatingFilePtr file;
+ int ret = -1;
+ char buf[512];
+
+ if (testRotatingFileInitFiles((off_t)-1,
+ (off_t)-1,
+ (off_t)-1) < 0)
+ return -1;
+
+ file = virRotatingFileNew(FILENAME,
+ 1024,
+ 2,
+ false,
+ 0700);
+ if (!file)
+ goto cleanup;
+
+ if (testRotatingFileAssertFiles(0,
+ (off_t)-1,
+ (off_t)-1) < 0)
+ goto cleanup;
+
+ memset(buf, 0x5e, sizeof(buf));
+
+ virRotatingFileWrite(file, buf, sizeof(buf));
+
+ if (testRotatingFileAssertFiles(sizeof(buf),
+ (off_t)-1,
+ (off_t)-1) < 0)
+ goto cleanup;
+
+ ret = 0;
+ cleanup:
+ virRotatingFileFree(file);
+ unlink(FILENAME);
+ unlink(FILENAME0);
+ unlink(FILENAME1);
+ return ret;
+}
+
+
+static int testRotatingFileAppend(const void *data ATTRIBUTE_UNUSED)
+{
+ virRotatingFilePtr file;
+ int ret = -1;
+ char buf[512];
+
+ if (testRotatingFileInitFiles(512,
+ (off_t)-1,
+ (off_t)-1) < 0)
+ return -1;
+
+ file = virRotatingFileNew(FILENAME,
+ 1024,
+ 2,
+ false,
+ 0700);
+ if (!file)
+ goto cleanup;
+
+ if (testRotatingFileAssertFiles(512,
+ (off_t)-1,
+ (off_t)-1) < 0)
+ goto cleanup;
+
+ memset(buf, 0x5e, sizeof(buf));
+
+ virRotatingFileWrite(file, buf, sizeof(buf));
+
+ if (testRotatingFileAssertFiles(1024,
+ (off_t)-1,
+ (off_t)-1) < 0)
+ goto cleanup;
+
+ ret = 0;
+ cleanup:
+ virRotatingFileFree(file);
+ unlink(FILENAME);
+ unlink(FILENAME0);
+ unlink(FILENAME1);
+ return ret;
+}
+
+
+static int testRotatingFileTruncate(const void *data ATTRIBUTE_UNUSED)
+{
+ virRotatingFilePtr file;
+ int ret = -1;
+ char buf[512];
+
+ if (testRotatingFileInitFiles(512,
+ (off_t)-1,
+ (off_t)-1) < 0)
+ return -1;
+
+ file = virRotatingFileNew(FILENAME,
+ 1024,
+ 2,
+ true,
+ 0700);
+ if (!file)
+ goto cleanup;
+
+ if (testRotatingFileAssertFiles(0,
+ (off_t)-1,
+ (off_t)-1) < 0)
+ goto cleanup;
+
+ memset(buf, 0x5e, sizeof(buf));
+
+ virRotatingFileWrite(file, buf, sizeof(buf));
+
+ if (testRotatingFileAssertFiles(512,
+ (off_t)-1,
+ (off_t)-1) < 0)
+ goto cleanup;
+
+ ret = 0;
+ cleanup:
+ virRotatingFileFree(file);
+ unlink(FILENAME);
+ unlink(FILENAME0);
+ unlink(FILENAME1);
+ return ret;
+}
+
+
+static int testRotatingFileRolloverOne(const void *data ATTRIBUTE_UNUSED)
+{
+ virRotatingFilePtr file;
+ int ret = -1;
+ char buf[512];
+
+ if (testRotatingFileInitFiles((off_t)-1,
+ (off_t)-1,
+ (off_t)-1) < 0)
+ return -1;
+
+ file = virRotatingFileNew(FILENAME,
+ 1024,
+ 2,
+ false,
+ 0700);
+ if (!file)
+ goto cleanup;
+
+ if (testRotatingFileAssertFiles(0,
+ (off_t)-1,
+ (off_t)-1) < 0)
+ goto cleanup;
+
+ memset(buf, 0x5e, sizeof(buf));
+
+ virRotatingFileWrite(file, buf, sizeof(buf));
+ virRotatingFileWrite(file, buf, sizeof(buf));
+ virRotatingFileWrite(file, buf, sizeof(buf));
+
+ if (testRotatingFileAssertFiles(512,
+ 1024,
+ (off_t)-1) < 0)
+ goto cleanup;
+
+ ret = 0;
+ cleanup:
+ virRotatingFileFree(file);
+ unlink(FILENAME);
+ unlink(FILENAME0);
+ unlink(FILENAME1);
+ return ret;
+}
+
+
+static int testRotatingFileRolloverAppend(const void *data ATTRIBUTE_UNUSED)
+{
+ virRotatingFilePtr file;
+ int ret = -1;
+ char buf[512];
+
+ if (testRotatingFileInitFiles((off_t)768,
+ (off_t)-1,
+ (off_t)-1) < 0)
+ return -1;
+
+ file = virRotatingFileNew(FILENAME,
+ 1024,
+ 2,
+ false,
+ 0700);
+ if (!file)
+ goto cleanup;
+
+ if (testRotatingFileAssertFiles(768,
+ (off_t)-1,
+ (off_t)-1) < 0)
+ goto cleanup;
+
+ memset(buf, 0x5e, sizeof(buf));
+
+ virRotatingFileWrite(file, buf, sizeof(buf));
+
+ if (testRotatingFileAssertFiles(256,
+ 1024,
+ (off_t)-1) < 0)
+ goto cleanup;
+
+ ret = 0;
+ cleanup:
+ virRotatingFileFree(file);
+ unlink(FILENAME);
+ unlink(FILENAME0);
+ unlink(FILENAME1);
+ return ret;
+}
+
+
+static int testRotatingFileRolloverMany(const void *data ATTRIBUTE_UNUSED)
+{
+ virRotatingFilePtr file;
+ int ret = -1;
+ char buf[512];
+
+ if (testRotatingFileInitFiles((off_t)-1,
+ (off_t)-1,
+ (off_t)-1) < 0)
+ return -1;
+
+ file = virRotatingFileNew(FILENAME,
+ 1024,
+ 2,
+ false,
+ 0700);
+ if (!file)
+ goto cleanup;
+
+ if (testRotatingFileAssertFiles(0,
+ (off_t)-1,
+ (off_t)-1) < 0)
+ goto cleanup;
+
+ memset(buf, 0x5e, sizeof(buf));
+
+ virRotatingFileWrite(file, buf, sizeof(buf));
+ virRotatingFileWrite(file, buf, sizeof(buf));
+ virRotatingFileWrite(file, buf, sizeof(buf));
+ virRotatingFileWrite(file, buf, sizeof(buf));
+ virRotatingFileWrite(file, buf, sizeof(buf));
+ virRotatingFileWrite(file, buf, sizeof(buf));
+ virRotatingFileWrite(file, buf, sizeof(buf));
+
+ if (testRotatingFileAssertFiles(512,
+ 1024,
+ 1024) < 0)
+ goto cleanup;
+
+ ret = 0;
+ cleanup:
+ virRotatingFileFree(file);
+ unlink(FILENAME);
+ unlink(FILENAME0);
+ unlink(FILENAME1);
+ return ret;
+}
+
+
+static int
+mymain(void)
+{
+ int ret = 0;
+
+ if (virtTestRun("Rotating file new", testRotatingFileNew, NULL) < 0)
+ ret = -1;
+
+ if (virtTestRun("Rotating file append", testRotatingFileAppend, NULL) <
0)
+ ret = -1;
+
+ if (virtTestRun("Rotating file truncate", testRotatingFileTruncate, NULL)
< 0)
+ ret = -1;
+
+ if (virtTestRun("Rotating file rollover one", testRotatingFileRolloverOne,
NULL) < 0)
+ ret = -1;
+
+ if (virtTestRun("Rotating file rollover append",
testRotatingFileRolloverAppend, NULL) < 0)
+ ret = -1;
+
+ if (virtTestRun("Rotating file rollover many",
testRotatingFileRolloverMany, NULL) < 0)
+ ret = -1;
+
+ return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+#if WITH_SELINUX
+VIRT_TEST_MAIN_PRELOAD(mymain, abs_builddir
"/.libs/libsecurityselinuxhelper.so")
+#else
+VIRT_TEST_MAIN(mymain)
+#endif
--
2.5.0