[libvirt] [PATCH 0/5] Initial patches to introduce a virtlogd daemon

Currently we have stdout + stderr of QEMU guests setup to write to a log file (eg /var/log/libvirt/qemu/$GUEST.log). This is nice and simple, but it in fact opens the possibility of a malicious or accidental denial of service, whereby QEMU can write logs of data to stdio and thus fill up the host filesystem holding the log. Although we have logrotate policy in place, this is only processed once a day, so there is still a window where disk usage is not constrained. The only way to solve this is to not let QEMU directly write to the log file, instead connect its stdio to a pipe and copy data from the pipe to the real log file, performing file rollover when it reaches a certain size. If we do this, we need something to keep open the pipe for as long as QEMU is running. This can't be libvirtd since we expect libvirtd to be able to be stopped while QEMU is running. Thus we introduce a new single-purpose daemon virtlogd whose job is exclusively to deal with log file writing. This daemon has support for SIGUSR1 to tell it to re-exec itself while keeping open the pipe to QEMU so it can be safely upgraded while QEMU is running. This series switches QEMU to use virtlogd by default, but in case of problems we can revert back to the old direct file access by setting 'stdio_handler = "file"' in /etc/libvirt/qemu.conf This series is only the first step. The same problem exists when character devices are told to use the file backend. There is a further use case from OpenStack which that they want to allow the use of both a TCP backend and a file backend at the same time. The idea is that the serial port is to be used for an interactive console, so needs the TCP backend support, but we also want to be able to record all output on that serial port to a file for logging purposes. Thus, in a followup I will work on creating a new character device backend "logd" that will use virtlogd to provide this combined tcp+logfile facility for QEMU guests. Daniel P. Berrange (5): util: add API for writing to rotating files Import stripped down virtlockd code as basis of virtlogd logging: introduce log handling protocol logging: add client for virtlogd daemon qemu: add support for sending QEMU stdout/stderr to virtlogd .gitignore | 7 + cfg.mk | 6 +- include/libvirt/virterror.h | 1 + libvirt.spec.in | 24 +- po/POTFILES.in | 5 + src/Makefile.am | 176 +++++- src/libvirt_private.syms | 12 + src/logging/log_daemon.c | 1207 ++++++++++++++++++++++++++++++++++++ src/logging/log_daemon.h | 45 ++ src/logging/log_daemon_config.c | 203 ++++++ src/logging/log_daemon_config.h | 50 ++ src/logging/log_daemon_dispatch.c | 67 ++ src/logging/log_daemon_dispatch.h | 31 + src/logging/log_handler.c | 429 +++++++++++++ src/logging/log_handler.h | 46 ++ src/logging/log_manager.c | 197 ++++++ src/logging/log_manager.h | 42 ++ src/logging/log_protocol.x | 71 +++ src/logging/test_virtlogd.aug.in | 12 + src/logging/virtlogd.aug | 45 ++ src/logging/virtlogd.conf | 67 ++ src/logging/virtlogd.init.in | 94 +++ src/logging/virtlogd.pod.in | 162 +++++ src/logging/virtlogd.service.in | 17 + src/logging/virtlogd.socket.in | 8 + src/logging/virtlogd.sysconf | 3 + src/qemu/libvirtd_qemu.aug | 1 + src/qemu/qemu.conf | 15 + src/qemu/qemu_conf.c | 18 + src/qemu/qemu_conf.h | 1 + src/qemu/qemu_domain.c | 43 +- src/qemu/qemu_process.c | 42 +- src/qemu/test_libvirtd_qemu.aug.in | 1 + src/util/virerror.c | 1 + src/util/virrotatingfile.c | 237 +++++++ src/util/virrotatingfile.h | 44 ++ tests/Makefile.am | 6 + tests/virrotatingfiletest.c | 415 +++++++++++++ 38 files changed, 3802 insertions(+), 49 deletions(-) create mode 100644 src/logging/log_daemon.c create mode 100644 src/logging/log_daemon.h create mode 100644 src/logging/log_daemon_config.c create mode 100644 src/logging/log_daemon_config.h create mode 100644 src/logging/log_daemon_dispatch.c create mode 100644 src/logging/log_daemon_dispatch.h create mode 100644 src/logging/log_handler.c create mode 100644 src/logging/log_handler.h create mode 100644 src/logging/log_manager.c create mode 100644 src/logging/log_manager.h create mode 100644 src/logging/log_protocol.x create mode 100644 src/logging/test_virtlogd.aug.in create mode 100644 src/logging/virtlogd.aug create mode 100644 src/logging/virtlogd.conf create mode 100644 src/logging/virtlogd.init.in create mode 100644 src/logging/virtlogd.pod.in create mode 100644 src/logging/virtlogd.service.in create mode 100644 src/logging/virtlogd.socket.in create mode 100644 src/logging/virtlogd.sysconf create mode 100644 src/util/virrotatingfile.c create mode 100644 src/util/virrotatingfile.h create mode 100644 tests/virrotatingfiletest.c -- 2.5.0

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@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@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

On Tue, Nov 03, 2015 at 16:04:20 +0000, Daniel Berrange wrote:
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@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
[...]
+ + +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)
At least this function would deserve some documentation explaining at least how maxbackup works.
+{ + virRotatingFilePtr file; + +
One too many lines.
+ if (VIR_ALLOC(file) < 0) + goto error; + + if (VIR_STRDUP(file->path, path) < 0) + goto error; + + file->mode = mode; + file->maxbackup = maxbackup;
If maxbackup is set to 0 the rollover code below will become a no-op, wich is fine, but in virRotatingFileWrite you close the fd, open the same file in apend mode seek to the end and find out that is already full.
+ file->maxlen = maxlen; + + if (truncate && + virRotatingFileDelete(file) < 0) + goto error; + + if (virRotatingFileOpen(file) < 0) + goto error; + + return file; + + error: + virRotatingFileFree(file); + return NULL; +}
[...]
+ + +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; +}
Inconsistent spacing between funcs.
+ +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;
So the boundary on which the file will be broken is strictly decided on the number of bytes. Generaly this is fine but for logging it might break messages in half which isn't entirely nice. I'd suggest that either this function (or via a flag or a different function) will have logic that will peek into the last eg. 100 characters (perhaps from the end) of the logged string to see whether there's a newline and if there is, it will rollover the log files earlier. This will slightly decrease the logging capacity but in turn we'll get nicely broken error messages.
+ + 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; +}
The rest looks okay. Peter

On Wed, Nov 04, 2015 at 07:56:37AM +0100, Peter Krempa wrote:
On Tue, Nov 03, 2015 at 16:04:20 +0000, Daniel Berrange wrote:
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@redhat.com>
+ if (VIR_ALLOC(file) < 0) + goto error; + + if (VIR_STRDUP(file->path, path) < 0) + goto error; + + file->mode = mode; + file->maxbackup = maxbackup;
If maxbackup is set to 0 the rollover code below will become a no-op, wich is fine, but in virRotatingFileWrite you close the fd, open the same file in apend mode seek to the end and find out that is already full.
Hmm, yeah, good point. I had thought about requiring maxbackup > 0 but I guess it is nicer to just fix that edge case.
+ +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;
So the boundary on which the file will be broken is strictly decided on the number of bytes. Generaly this is fine but for logging it might break messages in half which isn't entirely nice.
I'd suggest that either this function (or via a flag or a different function) will have logic that will peek into the last eg. 100 characters (perhaps from the end) of the logged string to see whether there's a newline and if there is, it will rollover the log files earlier.
This will slightly decrease the logging capacity but in turn we'll get nicely broken error messages.
Yeah, I think I'll just add logic that always looks for presence of a \n in the last 128 bytes before the size limit, and rollover early on that if found. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

Copy the virtlockd codebase across to form the initial virlogd code. Simple search & replace of s/lock/log/ and gut the remote protocol & dispatcher. This gives us a daemon that starts up and listens for connections, but does nothing with them. --- .gitignore | 7 + cfg.mk | 4 +- include/libvirt/virterror.h | 1 + libvirt.spec.in | 24 +- po/POTFILES.in | 2 + src/Makefile.am | 167 +++++- src/logging/log_daemon.c | 1177 +++++++++++++++++++++++++++++++++++++ src/logging/log_daemon.h | 42 ++ src/logging/log_daemon_config.c | 203 +++++++ src/logging/log_daemon_config.h | 50 ++ src/logging/log_daemon_dispatch.c | 37 ++ src/logging/log_daemon_dispatch.h | 31 + src/logging/log_protocol.x | 22 + src/logging/test_virtlogd.aug.in | 12 + src/logging/virtlogd.aug | 45 ++ src/logging/virtlogd.conf | 67 +++ src/logging/virtlogd.init.in | 94 +++ src/logging/virtlogd.pod.in | 162 +++++ src/logging/virtlogd.service.in | 17 + src/logging/virtlogd.socket.in | 8 + src/logging/virtlogd.sysconf | 3 + src/util/virerror.c | 1 + 22 files changed, 2155 insertions(+), 21 deletions(-) create mode 100644 src/logging/log_daemon.c create mode 100644 src/logging/log_daemon.h create mode 100644 src/logging/log_daemon_config.c create mode 100644 src/logging/log_daemon_config.h create mode 100644 src/logging/log_daemon_dispatch.c create mode 100644 src/logging/log_daemon_dispatch.h create mode 100644 src/logging/log_protocol.x create mode 100644 src/logging/test_virtlogd.aug.in create mode 100644 src/logging/virtlogd.aug create mode 100644 src/logging/virtlogd.conf create mode 100644 src/logging/virtlogd.init.in create mode 100644 src/logging/virtlogd.pod.in create mode 100644 src/logging/virtlogd.service.in create mode 100644 src/logging/virtlogd.socket.in create mode 100644 src/logging/virtlogd.sysconf diff --git a/.gitignore b/.gitignore index 2d52a8f..7f3e253 100644 --- a/.gitignore +++ b/.gitignore @@ -137,6 +137,8 @@ /src/locking/qemu-lockd.conf /src/locking/qemu-sanlock.conf /src/locking/test_libvirt_sanlock.aug +/src/logging/log_daemon_dispatch_stubs.h +/src/logging/log_protocol.[ch] /src/lxc/lxc_controller_dispatch.h /src/lxc/lxc_monitor_dispatch.h /src/lxc/lxc_monitor_protocol.c @@ -150,12 +152,17 @@ /src/rpc/virnetprotocol.[ch] /src/test_libvirt*.aug /src/test_virtlockd.aug +/src/test_virtlogd.aug /src/util/virkeymaps.h /src/virt-aa-helper /src/virtlockd /src/virtlockd.8 /src/virtlockd.8.in /src/virtlockd.init +/src/virtlogd +/src/virtlogd.8 +/src/virtlogd.8.in +/src/virtlogd.init /tests/*.log /tests/*.pid /tests/*.trs diff --git a/cfg.mk b/cfg.mk index a9bba38..cf18a84 100644 --- a/cfg.mk +++ b/cfg.mk @@ -1098,7 +1098,7 @@ $(srcdir)/src/admin/admin_client.h: $(srcdir)/src/admin/admin_protocol.x # List all syntax-check exemptions: exclude_file_name_regexp--sc_avoid_strcase = ^tools/vsh\.h$$ -_src1=libvirt-stream|fdstream|qemu/qemu_monitor|util/(vircommand|virfile)|xen/xend_internal|rpc/virnetsocket|lxc/lxc_controller|locking/lock_daemon +_src1=libvirt-stream|fdstream|qemu/qemu_monitor|util/(vircommand|virfile)|xen/xend_internal|rpc/virnetsocket|lxc/lxc_controller|locking/lock_daemon|logging/log_daemon _test1=shunloadtest|virnettlscontexttest|virnettlssessiontest|vircgroupmock exclude_file_name_regexp--sc_avoid_write = \ ^(src/($(_src1))|daemon/libvirtd|tools/virsh-console|tests/($(_test1)))\.c$$ @@ -1133,7 +1133,7 @@ exclude_file_name_regexp--sc_prohibit_close = \ exclude_file_name_regexp--sc_prohibit_empty_lines_at_EOF = \ (^tests/(qemuhelp|nodeinfo|virpcitest)data/|\.diff$$) -_src2=src/(util/vircommand|libvirt|lxc/lxc_controller|locking/lock_daemon) +_src2=src/(util/vircommand|libvirt|lxc/lxc_controller|locking/lock_daemon|logging/log_daemon) exclude_file_name_regexp--sc_prohibit_fork_wrappers = \ (^($(_src2)|tests/testutils|daemon/libvirtd)\.c$$) diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h index f716cb9..0539e48 100644 --- a/include/libvirt/virterror.h +++ b/include/libvirt/virterror.h @@ -127,6 +127,7 @@ typedef enum { VIR_FROM_POLKIT = 60, /* Error from polkit code */ VIR_FROM_THREAD = 61, /* Error from thread utils */ VIR_FROM_ADMIN = 62, /* Error from admin backend */ + VIR_FROM_LOGGING = 63, /* Error from log manager */ # ifdef VIR_ENUM_SENTINELS VIR_ERR_DOMAIN_LAST diff --git a/libvirt.spec.in b/libvirt.spec.in index 469bfca..34125c0 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -1690,12 +1690,13 @@ exit 0 %if %{with_systemd} %if %{with_systemd_macros} - %systemd_post virtlockd.socket libvirtd.service libvirtd.socket + %systemd_post virtlockd.socket virtlogd.socket libvirtd.service libvirtd.socket %else if [ $1 -eq 1 ] ; then # Initial installation /bin/systemctl enable \ virtlockd.socket \ + virtlogd.socket \ libvirtd.service >/dev/null 2>&1 || : fi %endif @@ -1711,24 +1712,29 @@ fi %endif /sbin/chkconfig --add libvirtd +/sbin/chkconfig --add virtlogd /sbin/chkconfig --add virtlockd %endif %preun daemon %if %{with_systemd} %if %{with_systemd_macros} - %systemd_preun libvirtd.socket libvirtd.service virtlockd.socket virtlockd.service + %systemd_preun libvirtd.socket libvirtd.service virtlogd.socket virtlogd.service virtlockd.socket virtlockd.service %else if [ $1 -eq 0 ] ; then # Package removal, not upgrade /bin/systemctl --no-reload disable \ libvirtd.socket \ libvirtd.service \ + virtlogd.socket \ + virtlogd.service \ virtlockd.socket \ virtlockd.service > /dev/null 2>&1 || : /bin/systemctl stop \ libvirtd.socket \ libvirtd.service \ + virtlogd.socket \ + virtlogd.service \ virtlockd.socket \ virtlockd.service > /dev/null 2>&1 || : fi @@ -1737,6 +1743,8 @@ fi if [ $1 = 0 ]; then /sbin/service libvirtd stop 1>/dev/null 2>&1 /sbin/chkconfig --del libvirtd + /sbin/service virtlogd stop 1>/dev/null 2>&1 + /sbin/chkconfig --del virtlogd /sbin/service virtlockd stop 1>/dev/null 2>&1 /sbin/chkconfig --del virtlockd fi @@ -1747,11 +1755,13 @@ fi /bin/systemctl daemon-reload >/dev/null 2>&1 || : if [ $1 -ge 1 ] ; then /bin/systemctl reload-or-try-restart virtlockd.service >/dev/null 2>&1 || : + /bin/systemctl reload-or-try-restart virtlogd.service >/dev/null 2>&1 || : /bin/systemctl try-restart libvirtd.service >/dev/null 2>&1 || : fi %else if [ $1 -ge 1 ]; then /sbin/service virtlockd reload > /dev/null 2>&1 || : + /sbin/service virtlogd reload > /dev/null 2>&1 || : /sbin/service libvirtd condrestart > /dev/null 2>&1 fi %endif @@ -1761,6 +1771,7 @@ fi %triggerpostun daemon -- libvirt-daemon < 1.2.1 if [ "$1" -ge "1" ]; then /sbin/service virtlockd reload > /dev/null 2>&1 || : + /sbin/service virtlogd reload > /dev/null 2>&1 || : /sbin/service libvirtd condrestart > /dev/null 2>&1 fi %endif @@ -1917,16 +1928,21 @@ exit 0 %if %{with_systemd} %{_unitdir}/libvirtd.service %{_unitdir}/libvirtd.socket +%{_unitdir}/virtlogd.service +%{_unitdir}/virtlogd.socket %{_unitdir}/virtlockd.service %{_unitdir}/virtlockd.socket %else %{_sysconfdir}/rc.d/init.d/libvirtd +%{_sysconfdir}/rc.d/init.d/virtlogd %{_sysconfdir}/rc.d/init.d/virtlockd %endif %doc daemon/libvirtd.upstart %config(noreplace) %{_sysconfdir}/sysconfig/libvirtd +%config(noreplace) %{_sysconfdir}/sysconfig/virtlogd %config(noreplace) %{_sysconfdir}/sysconfig/virtlockd %config(noreplace) %{_sysconfdir}/libvirt/libvirtd.conf +%config(noreplace) %{_sysconfdir}/libvirt/virtlogd.conf %config(noreplace) %{_sysconfdir}/libvirt/virtlockd.conf %if 0%{?fedora} || 0%{?rhel} >= 6 %config(noreplace) %{_prefix}/lib/sysctl.d/60-libvirtd.conf @@ -1948,6 +1964,8 @@ exit 0 %{_datadir}/augeas/lenses/libvirtd.aug %{_datadir}/augeas/lenses/tests/test_libvirtd.aug +%{_datadir}/augeas/lenses/virtlogd.aug +%{_datadir}/augeas/lenses/tests/test_virtlogd.aug %{_datadir}/augeas/lenses/virtlockd.aug %{_datadir}/augeas/lenses/tests/test_virtlockd.aug %{_datadir}/augeas/lenses/libvirt_lockd.aug @@ -1974,9 +1992,11 @@ exit 0 %endif %attr(0755, root, root) %{_sbindir}/libvirtd +%attr(0755, root, root) %{_sbindir}/virtlogd %attr(0755, root, root) %{_sbindir}/virtlockd %{_mandir}/man8/libvirtd.8* +%{_mandir}/man8/virtlogd.8* %{_mandir}/man8/virtlockd.8* %if ! %{with_driver_modules} diff --git a/po/POTFILES.in b/po/POTFILES.in index 401ac6f..33bc258 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -78,6 +78,8 @@ src/locking/lock_driver_lockd.c src/locking/lock_driver_sanlock.c src/locking/lock_manager.c src/locking/sanlock_helper.c +src/logging/log_daemon.c +src/logging/log_daemon_config.c src/lxc/lxc_cgroup.c src/lxc/lxc_fuse.c src/lxc/lxc_hostdev.c diff --git a/src/Makefile.am b/src/Makefile.am index ee082ec..9f80f2b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -263,6 +263,41 @@ locking/lock_daemon_dispatch_stubs.h: $(LOCK_PROTOCOL) \ $(LOCK_PROTOCOL) > $(srcdir)/locking/lock_daemon_dispatch_stubs.h +LOG_PROTOCOL_GENERATED = \ + logging/log_protocol.h \ + logging/log_protocol.c \ + $(NULL) + +LOG_PROTOCOL = $(srcdir)/logging/log_protocol.x +EXTRA_DIST += $(LOG_PROTOCOL) \ + $(LOG_PROTOCOL_GENERATED) +BUILT_SOURCES += $(LOG_PROTOCOL_GENERATED) +MAINTAINERCLEANFILES += $(LOG_PROTOCOL_GENERATED) + +LOG_DAEMON_GENERATED = \ + logging/log_daemon_dispatch_stubs.h + $(NULL) + +BUILT_SOURCES += $(LOG_DAEMON_GENERATED) +EXTRA_DIST += $(LOG_DAEMON_GENERATED) +MAINTAINERCLEANFILES += $(LOG_DAEMON_GENERATED) + +LOG_DAEMON_SOURCES = \ + logging/log_daemon.h \ + logging/log_daemon.c \ + logging/log_daemon_config.h \ + logging/log_daemon_config.c \ + logging/log_daemon_dispatch.c \ + logging/log_daemon_dispatch.h \ + $(NULL) + +logging/log_daemon_dispatch_stubs.h: $(LOG_PROTOCOL) \ + $(srcdir)/rpc/gendispatch.pl Makefile.am + $(AM_V_GEN)perl -w $(srcdir)/rpc/gendispatch.pl --mode=server \ + virLogManagerProtocol VIR_LOG_MANAGER_PROTOCOL \ + $(LOG_PROTOCOL) > $(srcdir)/logging/log_daemon_dispatch_stubs.h + + NETDEV_CONF_SOURCES = \ conf/netdev_bandwidth_conf.h conf/netdev_bandwidth_conf.c \ conf/netdev_vport_profile_conf.h conf/netdev_vport_profile_conf.c \ @@ -1839,6 +1874,7 @@ check-local: check-augeas check-augeas: check-augeas-qemu check-augeas-lxc check-augeas-sanlock \ check-augeas-lockd check-augeas-virtlockd check-augeas-libxl + check-augeas-virtlogd AUG_GENTEST = $(PERL) $(top_srcdir)/build-aux/augeas-gentest.pl EXTRA_DIST += $(top_srcdir)/build-aux/augeas-gentest.pl @@ -1921,6 +1957,15 @@ else ! WITH_LIBXL check-augeas-libxl: endif ! WITH_LIBXL +test_virtlogd.aug: logging/test_virtlogd.aug.in \ + logging/virtlogd.conf $(AUG_GENTEST) + $(AM_V_GEN)$(AUG_GENTEST) $(srcdir)/logging/virtlogd.conf $< $@ + +check-augeas-virtlogd: test_virtlogd.aug + $(AM_V_GEN)if test -x '$(AUGPARSE)'; then \ + '$(AUGPARSE)' -I $(srcdir)/logging test_virtlogd.aug; \ + fi + # # Build our version script. This is composed of three parts: # @@ -2345,7 +2390,7 @@ locking/%-lockd.conf: $(srcdir)/locking/lockd.conf cp $< $@ -sbin_PROGRAMS = virtlockd +sbin_PROGRAMS = virtlockd virtlogd virtlockd_SOURCES = \ $(LOCK_DAEMON_SOURCES) \ @@ -2374,40 +2419,79 @@ if WITH_DTRACE_PROBES virtlockd_LDADD += libvirt_probes.lo endif WITH_DTRACE_PROBES + +virtlogd_SOURCES = \ + $(LOG_DAEMON_SOURCES) \ + $(LOG_PROTOCOL_GENERATED) \ + $(LOG_DAEMON_GENERATED) \ + $(NULL) +virtlogd_CFLAGS = \ + $(AM_CFLAGS) \ + $(PIE_CFLAGS) \ + $(XDR_CFLAGS) \ + $(NULL) +virtlogd_LDFLAGS = \ + $(AM_LDFLAGS) \ + $(PIE_LDFLAGS) \ + $(CYGWIN_EXTRA_LDFLAGS) \ + $(MINGW_EXTRA_LDFLAGS) \ + $(NULL) +virtlogd_LDADD = \ + libvirt-net-rpc-server.la \ + libvirt-net-rpc.la \ + libvirt_util.la \ + ../gnulib/lib/libgnu.la \ + $(CYGWIN_EXTRA_LIBADD) \ + $(NULL) +if WITH_DTRACE_PROBES +virtlogd_LDADD += libvirt_probes.lo +endif WITH_DTRACE_PROBES + else ! WITH_LIBVIRTD EXTRA_DIST += $(LOCK_DAEMON_SOURCES) \ - $(LOCK_DRIVER_LOCKD_SOURCES) + $(LOCK_DRIVER_LOCKD_SOURCES) \ + $(LOG_DAEMON_SOURCES) endif ! WITH_LIBVIRTD -EXTRA_DIST += locking/virtlockd.sysconf \ +EXTRA_DIST += \ + locking/virtlockd.sysconf \ locking/lockd.conf \ locking/libvirt_lockd.aug \ - locking/test_libvirt_lockd.aug.in + locking/test_libvirt_lockd.aug.in \ + logging/virtlogd.sysconf \ + logging/libvirt_logd.aug \ + logging/test_libvirt_logd.aug.in install-sysconfig: $(MKDIR_P) $(DESTDIR)$(sysconfdir)/sysconfig $(INSTALL_DATA) $(srcdir)/locking/virtlockd.sysconf \ $(DESTDIR)$(sysconfdir)/sysconfig/virtlockd + $(INSTALL_DATA) $(srcdir)/logging/virtlogd.sysconf \ + $(DESTDIR)$(sysconfdir)/sysconfig/virtlogd uninstall-sysconfig: + rm -f $(DESTDIR)$(sysconfdir)/sysconfig/virtlogd rm -f $(DESTDIR)$(sysconfdir)/sysconfig/virtlockd rmdir $(DESTDIR)$(sysconfdir)/sysconfig || : -EXTRA_DIST += locking/virtlockd.init.in +EXTRA_DIST += locking/virtlockd.init.in logging/virtlogd.init.in if WITH_LIBVIRTD if LIBVIRT_INIT_SCRIPT_RED_HAT -install-init:: virtlockd.init install-sysconfig +install-init:: virtlockd.init virtlogd.init install-sysconfig $(MKDIR_P) $(DESTDIR)$(sysconfdir)/rc.d/init.d $(INSTALL_SCRIPT) virtlockd.init \ $(DESTDIR)$(sysconfdir)/rc.d/init.d/virtlockd + $(INSTALL_SCRIPT) virtlogd.init \ + $(DESTDIR)$(sysconfdir)/rc.d/init.d/virtlogd uninstall-init:: uninstall-sysconfig rm -f $(DESTDIR)$(sysconfdir)/rc.d/init.d/virtlockd + rm -f $(DESTDIR)$(sysconfdir)/rc.d/init.d/virtlogd rmdir $(DESTDIR)$(sysconfdir)/rc.d/init.d || : -BUILT_SOURCES += virtlockd.init -DISTCLEANFILES += virtlockd.init +BUILT_SOURCES += virtlockd.init virtlogd.init +DISTCLEANFILES += virtlockd.init virtlogd.init else ! LIBVIRT_INIT_SCRIPT_RED_HAT install-init:: uninstall-init:: @@ -2426,6 +2510,15 @@ virtlockd.init: locking/virtlockd.init.in $(top_builddir)/config.status chmod a+x $@-t && \ mv $@-t $@ +virtlogd.init: logging/virtlogd.init.in $(top_builddir)/config.status + $(AM_V_GEN)sed \ + -e 's|[@]localstatedir[@]|$(localstatedir)|g' \ + -e 's|[@]sbindir[@]|$(sbindir)|g' \ + -e 's|[@]sysconfdir[@]|$(sysconfdir)|g' \ + < $< > $@-t && \ + chmod a+x $@-t && \ + mv $@-t $@ + POD2MAN = pod2man -c "Virtualization Support" \ -r "$(PACKAGE)-$(VERSION)" -s 8 @@ -2440,17 +2533,29 @@ virtlockd.8: $(srcdir)/virtlockd.8.in < $< > $@-t && \ mv $@-t $@ +$(srcdir)/virtlogd.8.in: logging/virtlogd.pod.in $(top_srcdir)/configure.ac + $(AM_V_GEN)$(POD2MAN) --name VIRTLOGD $< $@ \ + && if grep 'POD ERROR' $@ ; then rm $@; exit 1; fi + +virtlogd.8: $(srcdir)/virtlogd.8.in + $(AM_V_GEN)sed \ + -e 's|[@]sysconfdir[@]|$(sysconfdir)|g' \ + -e 's|[@]localstatedir[@]|$(localstatedir)|g' \ + < $< > $@-t && \ + mv $@-t $@ + if WITH_LIBVIRTD -man8_MANS = virtlockd.8 +man8_MANS = virtlockd.8 virtlogd.8 -conf_DATA += locking/virtlockd.conf +conf_DATA += locking/virtlockd.conf logging/virtlogd.conf -augeas_DATA += locking/virtlockd.aug -augeastest_DATA += test_virtlockd.aug +augeas_DATA += locking/virtlockd.aug logging/virtlogd.aug +augeastest_DATA += test_virtlockd.aug test_virtlogd.aug endif WITH_LIBVIRTD -CLEANFILES += test_virtlockd.aug virtlockd.8 -MAINTAINERCLEANFILES += $(srcdir)/virtlockd.8.in +CLEANFILES += test_virtlockd.aug virtlockd.8 \ + test_virtlogd.aug virtlogd.8 +MAINTAINERCLEANFILES += $(srcdir)/virtlockd.8.in $(srcdir)/virtlogd.8.in EXTRA_DIST += \ locking/virtlockd.service.in \ @@ -2460,6 +2565,13 @@ EXTRA_DIST += \ locking/virtlockd.aug \ locking/virtlockd.conf \ locking/test_virtlockd.aug.in \ + logging/virtlogd.service.in \ + logging/virtlogd.socket.in \ + logging/virtlogd.pod.in \ + virtlogd.8.in \ + logging/virtlogd.aug \ + logging/virtlogd.conf \ + logging/test_virtlogd.aug.in \ $(NULL) @@ -2468,19 +2580,28 @@ if LIBVIRT_INIT_SCRIPT_SYSTEMD SYSTEMD_UNIT_DIR = $(prefix)/lib/systemd/system -BUILT_SOURCES += virtlockd.service virtlockd.socket -DISTCLEANFILES += virtlockd.service virtlockd.socket +BUILT_SOURCES += virtlockd.service virtlockd.socket \ + virtlogd.service virtlogd.socket +DISTCLEANFILES += virtlockd.service virtlockd.socket \ + virtlogd.service virtlogd.socket -install-systemd: virtlockd.service virtlockd.socket install-sysconfig +install-systemd: virtlockd.service virtlockd.socket \ + virtlogd.service virtlogd.socket install-sysconfig $(MKDIR_P) $(DESTDIR)$(SYSTEMD_UNIT_DIR) $(INSTALL_DATA) virtlockd.service \ $(DESTDIR)$(SYSTEMD_UNIT_DIR)/ $(INSTALL_DATA) virtlockd.socket \ $(DESTDIR)$(SYSTEMD_UNIT_DIR)/ + $(INSTALL_DATA) virtlogd.service \ + $(DESTDIR)$(SYSTEMD_UNIT_DIR)/ + $(INSTALL_DATA) virtlogd.socket \ + $(DESTDIR)$(SYSTEMD_UNIT_DIR)/ uninstall-systemd: uninstall-sysconfig rm -f $(DESTDIR)$(SYSTEMD_UNIT_DIR)/virtlockd.service \ $(DESTDIR)$(SYSTEMD_UNIT_DIR)/virtlockd.socket + rm -f $(DESTDIR)$(SYSTEMD_UNIT_DIR)/virtlogd.service \ + $(DESTDIR)$(SYSTEMD_UNIT_DIR)/virtlogd.socket rmdir $(DESTDIR)$(SYSTEMD_UNIT_DIR) || : else ! LIBVIRT_INIT_SCRIPT_SYSTEMD install-systemd: @@ -2503,6 +2624,18 @@ virtlockd.socket: locking/virtlockd.socket.in $(top_builddir)/config.status < $< > $@-t && \ mv $@-t $@ +virtlogd.service: logging/virtlogd.service.in $(top_builddir)/config.status + $(AM_V_GEN)sed \ + -e 's|[@]sbindir[@]|$(sbindir)|g' \ + < $< > $@-t && \ + mv $@-t $@ + +virtlogd.socket: logging/virtlogd.socket.in $(top_builddir)/config.status + $(AM_V_GEN)sed \ + -e 's|[@]localstatedir[@]|$(localstatedir)|g' \ + < $< > $@-t && \ + mv $@-t $@ + if WITH_SANLOCK lockdriver_LTLIBRARIES += sanlock.la diff --git a/src/logging/log_daemon.c b/src/logging/log_daemon.c new file mode 100644 index 0000000..184076c --- /dev/null +++ b/src/logging/log_daemon.c @@ -0,0 +1,1177 @@ +/* + * log_daemon.c: log management daemon + * + * Copyright (C) 2006-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@redhat.com> + */ + +#include <config.h> + +#include <unistd.h> +#include <fcntl.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <getopt.h> +#include <stdlib.h> +#include <locale.h> + + +#include "log_daemon.h" +#include "log_daemon_config.h" +#include "virutil.h" +#include "virfile.h" +#include "virpidfile.h" +#include "virprocess.h" +#include "virerror.h" +#include "virlog.h" +#include "viralloc.h" +#include "virconf.h" +#include "rpc/virnetdaemon.h" +#include "virrandom.h" +#include "virhash.h" +#include "viruuid.h" +#include "virstring.h" + +#include "log_daemon_dispatch.h" +#include "log_protocol.h" + +#include "configmake.h" + +#define VIR_FROM_THIS VIR_FROM_LOGGING + +VIR_LOG_INIT("logging.log_daemon"); + +struct _virLogDaemon { + virMutex lock; + virNetDaemonPtr dmn; + virNetServerPtr srv; +}; + +virLogDaemonPtr logDaemon = NULL; + +static bool execRestart; + +enum { + VIR_LOG_DAEMON_ERR_NONE = 0, + VIR_LOG_DAEMON_ERR_PIDFILE, + VIR_LOG_DAEMON_ERR_RUNDIR, + VIR_LOG_DAEMON_ERR_INIT, + VIR_LOG_DAEMON_ERR_SIGNAL, + VIR_LOG_DAEMON_ERR_PRIVS, + VIR_LOG_DAEMON_ERR_NETWORK, + VIR_LOG_DAEMON_ERR_CONFIG, + VIR_LOG_DAEMON_ERR_HOOKS, + VIR_LOG_DAEMON_ERR_REEXEC, + + VIR_LOG_DAEMON_ERR_LAST +}; + +VIR_ENUM_DECL(virDaemonErr) +VIR_ENUM_IMPL(virDaemonErr, VIR_LOG_DAEMON_ERR_LAST, + "Initialization successful", + "Unable to obtain pidfile", + "Unable to create rundir", + "Unable to initialize libvirt", + "Unable to setup signal handlers", + "Unable to drop privileges", + "Unable to initialize network sockets", + "Unable to load configuration file", + "Unable to look for hook scripts", + "Unable to re-execute daemon"); + +static void * +virLogDaemonClientNew(virNetServerClientPtr client, + void *opaque); +static void +virLogDaemonClientFree(void *opaque); + +static void * +virLogDaemonClientNewPostExecRestart(virNetServerClientPtr client, + virJSONValuePtr object, + void *opaque); +static virJSONValuePtr +virLogDaemonClientPreExecRestart(virNetServerClientPtr client, + void *opaque); + +static void +virLogDaemonFree(virLogDaemonPtr logd) +{ + if (!logd) + return; + + virObjectUnref(logd->srv); + virObjectUnref(logd->dmn); + + VIR_FREE(logd); +} + + +static virLogDaemonPtr +virLogDaemonNew(virLogDaemonConfigPtr config, bool privileged) +{ + virLogDaemonPtr logd; + + if (VIR_ALLOC(logd) < 0) + return NULL; + + if (virMutexInit(&logd->lock) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to initialize mutex")); + VIR_FREE(logd); + return NULL; + } + + if (!(logd->srv = virNetServerNew(1, 1, 0, config->max_clients, + config->max_clients, -1, 0, + NULL, + virLogDaemonClientNew, + virLogDaemonClientPreExecRestart, + virLogDaemonClientFree, + (void*)(intptr_t)(privileged ? 0x1 : 0x0)))) + goto error; + + if (!(logd->dmn = virNetDaemonNew()) || + virNetDaemonAddServer(logd->dmn, logd->srv) < 0) + goto error; + + return logd; + + error: + virLogDaemonFree(logd); + return NULL; +} + + +static virLogDaemonPtr +virLogDaemonNewPostExecRestart(virJSONValuePtr object, bool privileged) +{ + virLogDaemonPtr logd; + virJSONValuePtr child; + + if (VIR_ALLOC(logd) < 0) + return NULL; + + if (virMutexInit(&logd->lock) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to initialize mutex")); + VIR_FREE(logd); + return NULL; + } + + if (!(child = virJSONValueObjectGet(object, "daemon"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Malformed daemon data from JSON file")); + goto error; + } + + if (!(logd->dmn = virNetDaemonNewPostExecRestart(child))) + goto error; + + if (!(logd->srv = virNetDaemonAddServerPostExec(logd->dmn, + virLogDaemonClientNew, + virLogDaemonClientNewPostExecRestart, + virLogDaemonClientPreExecRestart, + virLogDaemonClientFree, + (void*)(intptr_t)(privileged ? 0x1 : 0x0)))) + goto error; + + return logd; + + error: + virLogDaemonFree(logd); + return NULL; +} + + +static int +virLogDaemonForkIntoBackground(const char *argv0) +{ + int statuspipe[2]; + if (pipe(statuspipe) < 0) + return -1; + + pid_t pid = fork(); + switch (pid) { + case 0: + { + int stdinfd = -1; + int stdoutfd = -1; + int nextpid; + + VIR_FORCE_CLOSE(statuspipe[0]); + + if ((stdinfd = open("/dev/null", O_RDONLY)) < 0) + goto cleanup; + if ((stdoutfd = open("/dev/null", O_WRONLY)) < 0) + goto cleanup; + if (dup2(stdinfd, STDIN_FILENO) != STDIN_FILENO) + goto cleanup; + if (dup2(stdoutfd, STDOUT_FILENO) != STDOUT_FILENO) + goto cleanup; + if (dup2(stdoutfd, STDERR_FILENO) != STDERR_FILENO) + goto cleanup; + if (VIR_CLOSE(stdinfd) < 0) + goto cleanup; + if (VIR_CLOSE(stdoutfd) < 0) + goto cleanup; + + if (setsid() < 0) + goto cleanup; + + nextpid = fork(); + switch (nextpid) { + case 0: + return statuspipe[1]; + case -1: + return -1; + default: + _exit(0); + } + + cleanup: + VIR_FORCE_CLOSE(stdoutfd); + VIR_FORCE_CLOSE(stdinfd); + return -1; + + } + + case -1: + return -1; + + default: + { + int got, exitstatus = 0; + int ret; + char status; + + VIR_FORCE_CLOSE(statuspipe[1]); + + /* We wait to make sure the first child forked successfully */ + if ((got = waitpid(pid, &exitstatus, 0)) < 0 || + got != pid || + exitstatus != 0) { + return -1; + } + + /* Now block until the second child initializes successfully */ + again: + ret = read(statuspipe[0], &status, 1); + if (ret == -1 && errno == EINTR) + goto again; + + if (ret == 1 && status != 0) { + fprintf(stderr, + _("%s: error: %s. Check /var/log/messages or run without " + "--daemon for more info.\n"), argv0, + virDaemonErrTypeToString(status)); + } + _exit(ret == 1 && status == 0 ? 0 : 1); + } + } +} + + +static int +virLogDaemonUnixSocketPaths(bool privileged, + char **sockfile) +{ + if (privileged) { + if (VIR_STRDUP(*sockfile, LOCALSTATEDIR "/run/libvirt/virtlogd-sock") < 0) + goto error; + } else { + char *rundir = NULL; + mode_t old_umask; + + if (!(rundir = virGetUserRuntimeDirectory())) + goto error; + + old_umask = umask(077); + if (virFileMakePath(rundir) < 0) { + umask(old_umask); + goto error; + } + umask(old_umask); + + if (virAsprintf(sockfile, "%s/virtlogd-sock", rundir) < 0) { + VIR_FREE(rundir); + goto error; + } + + VIR_FREE(rundir); + } + return 0; + + error: + return -1; +} + + +static void +virLogDaemonErrorHandler(void *opaque ATTRIBUTE_UNUSED, + virErrorPtr err ATTRIBUTE_UNUSED) +{ + /* Don't do anything, since logging infrastructure already + * took care of reporting the error */ +} + + +/* + * Set up the logging environment + * By default if daemonized all errors go to the logfile libvirtd.log, + * but if verbose or error debugging is asked for then also output + * informational and debug messages. Default size if 64 kB. + */ +static int +virLogDaemonSetupLogging(virLogDaemonConfigPtr config, + bool privileged, + bool verbose, + bool godaemon) +{ + virLogReset(); + + /* + * Libvirtd's order of precedence is: + * cmdline > environment > config + * + * In order to achieve this, we must process configuration in + * different order for the log level versus the filters and + * outputs. Because filters and outputs append, we have to look at + * the environment first and then only check the config file if + * there was no result from the environment. The default output is + * then applied only if there was no setting from either of the + * first two. Because we don't have a way to determine if the log + * level has been set, we must process variables in the opposite + * order, each one overriding the previous. + */ + if (config->log_level != 0) + virLogSetDefaultPriority(config->log_level); + + virLogSetFromEnv(); + + if (virLogGetNbFilters() == 0) + virLogParseFilters(config->log_filters); + + if (virLogGetNbOutputs() == 0) + virLogParseOutputs(config->log_outputs); + + /* + * If no defined outputs, and either running + * as daemon or not on a tty, then first try + * to direct it to the systemd journal + * (if it exists).... + */ + if (virLogGetNbOutputs() == 0 && + (godaemon || !isatty(STDIN_FILENO))) { + char *tmp; + if (access("/run/systemd/journal/socket", W_OK) >= 0) { + if (virAsprintf(&tmp, "%d:journald", virLogGetDefaultPriority()) < 0) + goto error; + virLogParseOutputs(tmp); + VIR_FREE(tmp); + } + } + + /* + * otherwise direct to libvirtd.log when running + * as daemon. Otherwise the default output is stderr. + */ + if (virLogGetNbOutputs() == 0) { + char *tmp = NULL; + + if (godaemon) { + if (privileged) { + if (virAsprintf(&tmp, "%d:file:%s/log/libvirt/virtlogd.log", + virLogGetDefaultPriority(), + LOCALSTATEDIR) == -1) + goto error; + } else { + char *logdir = virGetUserCacheDirectory(); + mode_t old_umask; + + if (!logdir) + goto error; + + old_umask = umask(077); + if (virFileMakePath(logdir) < 0) { + umask(old_umask); + goto error; + } + umask(old_umask); + + if (virAsprintf(&tmp, "%d:file:%s/virtlogd.log", + virLogGetDefaultPriority(), logdir) == -1) { + VIR_FREE(logdir); + goto error; + } + VIR_FREE(logdir); + } + } else { + if (virAsprintf(&tmp, "%d:stderr", virLogGetDefaultPriority()) < 0) + goto error; + } + virLogParseOutputs(tmp); + VIR_FREE(tmp); + } + + /* + * Command line override for --verbose + */ + if ((verbose) && (virLogGetDefaultPriority() > VIR_LOG_INFO)) + virLogSetDefaultPriority(VIR_LOG_INFO); + + return 0; + + error: + return -1; +} + + + +/* Display version information. */ +static void +virLogDaemonVersion(const char *argv0) +{ + printf("%s (%s) %s\n", argv0, PACKAGE_NAME, PACKAGE_VERSION); +} + +static void +virLogDaemonShutdownHandler(virNetDaemonPtr dmn, + siginfo_t *sig ATTRIBUTE_UNUSED, + void *opaque ATTRIBUTE_UNUSED) +{ + virNetDaemonQuit(dmn); +} + +static void +virLogDaemonExecRestartHandler(virNetDaemonPtr dmn, + siginfo_t *sig ATTRIBUTE_UNUSED, + void *opaque ATTRIBUTE_UNUSED) +{ + execRestart = true; + virNetDaemonQuit(dmn); +} + +static int +virLogDaemonSetupSignals(virNetDaemonPtr dmn) +{ + if (virNetDaemonAddSignalHandler(dmn, SIGINT, virLogDaemonShutdownHandler, NULL) < 0) + return -1; + if (virNetDaemonAddSignalHandler(dmn, SIGQUIT, virLogDaemonShutdownHandler, NULL) < 0) + return -1; + if (virNetDaemonAddSignalHandler(dmn, SIGTERM, virLogDaemonShutdownHandler, NULL) < 0) + return -1; + if (virNetDaemonAddSignalHandler(dmn, SIGUSR1, virLogDaemonExecRestartHandler, NULL) < 0) + return -1; + return 0; +} + + +static int +virLogDaemonSetupNetworkingSystemD(virNetServerPtr srv) +{ + virNetServerServicePtr svc; + unsigned int nfds; + + if ((nfds = virGetListenFDs()) == 0) + return 0; + if (nfds > 1) + VIR_DEBUG("Too many (%d) file descriptors from systemd", nfds); + nfds = 1; + + /* Systemd passes FDs, starting immediately after stderr, + * so the first FD we'll get is '3'. */ + if (!(svc = virNetServerServiceNewFD(3, 0, +#if WITH_GNUTLS + NULL, +#endif + false, 0, 1))) + return -1; + + if (virNetServerAddService(srv, svc, NULL) < 0) { + virObjectUnref(svc); + return -1; + } + return 1; +} + + +static int +virLogDaemonSetupNetworkingNative(virNetServerPtr srv, const char *sock_path) +{ + virNetServerServicePtr svc; + + VIR_DEBUG("Setting up networking natively"); + + if (!(svc = virNetServerServiceNewUNIX(sock_path, 0700, 0, 0, +#if WITH_GNUTLS + NULL, +#endif + false, 0, 1))) + return -1; + + if (virNetServerAddService(srv, svc, NULL) < 0) { + virObjectUnref(svc); + return -1; + } + return 0; +} + + +static void +virLogDaemonClientFree(void *opaque) +{ + virLogDaemonClientPtr priv = opaque; + + if (!priv) + return; + + VIR_DEBUG("priv=%p client=%lld", + priv, + (unsigned long long)priv->clientPid); + + virMutexDestroy(&priv->lock); + VIR_FREE(priv); +} + + +static void * +virLogDaemonClientNew(virNetServerClientPtr client, + void *opaque) +{ + virLogDaemonClientPtr priv; + uid_t clientuid; + gid_t clientgid; + unsigned long long timestamp; + bool privileged = opaque != NULL; + + if (VIR_ALLOC(priv) < 0) + return NULL; + + if (virMutexInit(&priv->lock) < 0) { + VIR_FREE(priv); + virReportSystemError(errno, "%s", _("unable to init mutex")); + return NULL; + } + + if (virNetServerClientGetUNIXIdentity(client, + &clientuid, + &clientgid, + &priv->clientPid, + ×tamp) < 0) + goto error; + + VIR_DEBUG("New client pid %llu uid %llu", + (unsigned long long)priv->clientPid, + (unsigned long long)clientuid); + + if (!privileged) { + if (geteuid() != clientuid) { + virReportRestrictedError(_("Disallowing client %llu with uid %llu"), + (unsigned long long)priv->clientPid, + (unsigned long long)clientuid); + goto error; + } + } else { + if (clientuid != 0) { + virReportRestrictedError(_("Disallowing client %llu with uid %llu"), + (unsigned long long)priv->clientPid, + (unsigned long long)clientuid); + goto error; + } + } + + return priv; + + error: + virMutexDestroy(&priv->lock); + VIR_FREE(priv); + return NULL; +} + + +static void * +virLogDaemonClientNewPostExecRestart(virNetServerClientPtr client, + virJSONValuePtr object ATTRIBUTE_UNUSED, + void *opaque) +{ + virLogDaemonClientPtr priv = virLogDaemonClientNew(client, opaque); + + if (!priv) + return NULL; + + return priv; +} + + +static virJSONValuePtr +virLogDaemonClientPreExecRestart(virNetServerClientPtr client ATTRIBUTE_UNUSED, + void *opaque ATTRIBUTE_UNUSED) +{ + virJSONValuePtr object = virJSONValueNewObject(); + + if (!object) + return NULL; + + return object; +} + + +static int +virLogDaemonExecRestartStatePath(bool privileged, + char **state_file) +{ + if (privileged) { + if (VIR_STRDUP(*state_file, LOCALSTATEDIR "/run/virtlogd-restart-exec.json") < 0) + goto error; + } else { + char *rundir = NULL; + mode_t old_umask; + + if (!(rundir = virGetUserRuntimeDirectory())) + goto error; + + old_umask = umask(077); + if (virFileMakePath(rundir) < 0) { + umask(old_umask); + VIR_FREE(rundir); + goto error; + } + umask(old_umask); + + if (virAsprintf(state_file, "%s/virtlogd-restart-exec.json", rundir) < 0) { + VIR_FREE(rundir); + goto error; + } + + VIR_FREE(rundir); + } + + return 0; + + error: + return -1; +} + + +static char * +virLogDaemonGetExecRestartMagic(void) +{ + char *ret; + + ignore_value(virAsprintf(&ret, "%lld", (long long int)getpid())); + return ret; +} + + +static int +virLogDaemonPostExecRestart(const char *state_file, + const char *pid_file, + int *pid_file_fd, + bool privileged) +{ + const char *gotmagic; + char *wantmagic = NULL; + int ret = -1; + char *state = NULL; + virJSONValuePtr object = NULL; + + VIR_DEBUG("Running post-restart exec"); + + if (!virFileExists(state_file)) { + VIR_DEBUG("No restart state file %s present", + state_file); + ret = 0; + goto cleanup; + } + + if (virFileReadAll(state_file, + 1024 * 1024 * 10, /* 10 MB */ + &state) < 0) + goto cleanup; + + VIR_DEBUG("Loading state %s", state); + + if (!(object = virJSONValueFromString(state))) + goto cleanup; + + gotmagic = virJSONValueObjectGetString(object, "magic"); + if (!gotmagic) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing magic data in JSON document")); + goto cleanup; + } + + if (!(wantmagic = virLogDaemonGetExecRestartMagic())) + goto cleanup; + + if (STRNEQ(gotmagic, wantmagic)) { + VIR_WARN("Found restart exec file with old magic %s vs wanted %s", + gotmagic, wantmagic); + ret = 0; + goto cleanup; + } + + /* Re-claim PID file now as we will not be daemonizing */ + if (pid_file && + (*pid_file_fd = virPidFileAcquirePath(pid_file, false, getpid())) < 0) + goto cleanup; + + if (!(logDaemon = virLogDaemonNewPostExecRestart(object, privileged))) + goto cleanup; + + ret = 1; + + cleanup: + unlink(state_file); + VIR_FREE(wantmagic); + VIR_FREE(state); + virJSONValueFree(object); + return ret; +} + + +static int +virLogDaemonPreExecRestart(const char *state_file, + virNetDaemonPtr dmn, + char **argv) +{ + virJSONValuePtr child; + char *state = NULL; + int ret = -1; + virJSONValuePtr object; + char *magic; + virHashKeyValuePairPtr pairs = NULL; + + VIR_DEBUG("Running pre-restart exec"); + + if (!(object = virJSONValueNewObject())) + goto cleanup; + + if (!(child = virNetDaemonPreExecRestart(dmn))) + goto cleanup; + + if (virJSONValueObjectAppend(object, "daemon", child) < 0) { + virJSONValueFree(child); + goto cleanup; + } + + if (!(magic = virLogDaemonGetExecRestartMagic())) + goto cleanup; + + if (virJSONValueObjectAppendString(object, "magic", magic) < 0) { + VIR_FREE(magic); + goto cleanup; + } + + if (!(state = virJSONValueToString(object, true))) + goto cleanup; + + VIR_DEBUG("Saving state %s", state); + + if (virFileWriteStr(state_file, + state, 0700) < 0) { + virReportSystemError(errno, + _("Unable to save state file %s"), + state_file); + goto cleanup; + } + + if (execvp(argv[0], argv) < 0) { + virReportSystemError(errno, "%s", + _("Unable to restart self")); + goto cleanup; + } + + abort(); /* This should be impossible to reach */ + + cleanup: + VIR_FREE(pairs); + VIR_FREE(state); + virJSONValueFree(object); + return ret; +} + + +static void +virLogDaemonUsage(const char *argv0, bool privileged) +{ + fprintf(stderr, + _("\n" + "Usage:\n" + " %s [options]\n" + "\n" + "Options:\n" + " -h | --help Display program help:\n" + " -v | --verbose Verbose messages.\n" + " -d | --daemon Run as a daemon & write PID file.\n" + " -t | --timeout <secs> Exit after timeout period.\n" + " -f | --config <file> Configuration file.\n" + " -V | --version Display version information.\n" + " -p | --pid-file <file> Change name of PID file.\n" + "\n" + "libvirt lock management daemon:\n"), argv0); + + if (privileged) { + fprintf(stderr, + _("\n" + " Default paths:\n" + "\n" + " Configuration file (unless overridden by -f):\n" + " %s/libvirt/virtlogd.conf\n" + "\n" + " Sockets:\n" + " %s/run/libvirt/virtlogd-sock\n" + "\n" + " PID file (unless overridden by -p):\n" + " %s/run/virtlogd.pid\n" + "\n"), + SYSCONFDIR, + LOCALSTATEDIR, + LOCALSTATEDIR); + } else { + fprintf(stderr, "%s", + _("\n" + " Default paths:\n" + "\n" + " Configuration file (unless overridden by -f):\n" + " $XDG_CONFIG_HOME/libvirt/virtlogd.conf\n" + "\n" + " Sockets:\n" + " $XDG_RUNTIME_DIR/libvirt/virtlogd-sock\n" + "\n" + " PID file:\n" + " $XDG_RUNTIME_DIR/libvirt/virtlogd.pid\n" + "\n")); + } +} + +#define MAX_LISTEN 5 +int main(int argc, char **argv) { + virNetServerProgramPtr logProgram = NULL; + char *remote_config_file = NULL; + int statuswrite = -1; + int ret = 1; + int verbose = 0; + int godaemon = 0; + char *run_dir = NULL; + char *pid_file = NULL; + int pid_file_fd = -1; + char *sock_file = NULL; + int timeout = -1; /* -t: Shutdown timeout */ + char *state_file = NULL; + bool implicit_conf = false; + mode_t old_umask; + bool privileged = false; + virLogDaemonConfigPtr config = NULL; + int rv; + + struct option opts[] = { + { "verbose", no_argument, &verbose, 'v'}, + { "daemon", no_argument, &godaemon, 'd'}, + { "config", required_argument, NULL, 'f'}, + { "timeout", required_argument, NULL, 't'}, + { "pid-file", required_argument, NULL, 'p'}, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + {0, 0, 0, 0} + }; + + privileged = geteuid() == 0; + + if (setlocale(LC_ALL, "") == NULL || + bindtextdomain(PACKAGE, LOCALEDIR) == NULL || + textdomain(PACKAGE) == NULL || + virThreadInitialize() < 0 || + virErrorInitialize() < 0) { + fprintf(stderr, _("%s: initialization failed\n"), argv[0]); + exit(EXIT_FAILURE); + } + + while (1) { + int optidx = 0; + int c; + char *tmp; + + c = getopt_long(argc, argv, "ldf:p:t:vVh", opts, &optidx); + + if (c == -1) + break; + + switch (c) { + case 0: + /* Got one of the flags */ + break; + case 'v': + verbose = 1; + break; + case 'd': + godaemon = 1; + break; + + case 't': + if (virStrToLong_i(optarg, &tmp, 10, &timeout) != 0 + || timeout <= 0 + /* Ensure that we can multiply by 1000 without overflowing. */ + || timeout > INT_MAX / 1000) { + VIR_ERROR(_("Invalid value for timeout")); + exit(EXIT_FAILURE); + } + break; + + case 'p': + VIR_FREE(pid_file); + if (VIR_STRDUP_QUIET(pid_file, optarg) < 0) + goto no_memory; + break; + + case 'f': + VIR_FREE(remote_config_file); + if (VIR_STRDUP_QUIET(remote_config_file, optarg) < 0) + goto no_memory; + break; + + case 'V': + virLogDaemonVersion(argv[0]); + exit(EXIT_SUCCESS); + + case 'h': + virLogDaemonUsage(argv[0], privileged); + exit(EXIT_SUCCESS); + + case '?': + default: + virLogDaemonUsage(argv[0], privileged); + exit(EXIT_FAILURE); + } + } + + virFileActivateDirOverride(argv[0]); + + if (!(config = virLogDaemonConfigNew(privileged))) { + VIR_ERROR(_("Can't create initial configuration")); + exit(EXIT_FAILURE); + } + + /* No explicit config, so try and find a default one */ + if (remote_config_file == NULL) { + implicit_conf = true; + if (virLogDaemonConfigFilePath(privileged, + &remote_config_file) < 0) { + VIR_ERROR(_("Can't determine config path")); + exit(EXIT_FAILURE); + } + } + + /* Read the config file if it exists*/ + if (remote_config_file && + virLogDaemonConfigLoadFile(config, remote_config_file, implicit_conf) < 0) { + virErrorPtr err = virGetLastError(); + if (err && err->message) + VIR_ERROR(_("Can't load config file: %s: %s"), + err->message, remote_config_file); + else + VIR_ERROR(_("Can't load config file: %s"), remote_config_file); + exit(EXIT_FAILURE); + } + + if (virLogDaemonSetupLogging(config, privileged, verbose, godaemon) < 0) { + VIR_ERROR(_("Can't initialize logging")); + exit(EXIT_FAILURE); + } + + if (!pid_file && + virPidFileConstructPath(privileged, + LOCALSTATEDIR, + "virtlogd", + &pid_file) < 0) { + VIR_ERROR(_("Can't determine pid file path.")); + exit(EXIT_FAILURE); + } + VIR_DEBUG("Decided on pid file path '%s'", NULLSTR(pid_file)); + + if (virLogDaemonUnixSocketPaths(privileged, + &sock_file) < 0) { + VIR_ERROR(_("Can't determine socket paths")); + exit(EXIT_FAILURE); + } + VIR_DEBUG("Decided on socket paths '%s'", + sock_file); + + if (virLogDaemonExecRestartStatePath(privileged, + &state_file) < 0) { + VIR_ERROR(_("Can't determine restart state file path")); + exit(EXIT_FAILURE); + } + VIR_DEBUG("Decided on restart state file path '%s'", + state_file); + + /* Ensure the rundir exists (on tmpfs on some systems) */ + if (privileged) { + if (VIR_STRDUP_QUIET(run_dir, LOCALSTATEDIR "/run/libvirt") < 0) + goto no_memory; + } else { + if (!(run_dir = virGetUserRuntimeDirectory())) { + VIR_ERROR(_("Can't determine user directory")); + goto cleanup; + } + } + + if (privileged) + old_umask = umask(022); + else + old_umask = umask(077); + VIR_DEBUG("Ensuring run dir '%s' exists", run_dir); + if (virFileMakePath(run_dir) < 0) { + char ebuf[1024]; + VIR_ERROR(_("unable to create rundir %s: %s"), run_dir, + virStrerror(errno, ebuf, sizeof(ebuf))); + ret = VIR_LOG_DAEMON_ERR_RUNDIR; + goto cleanup; + } + umask(old_umask); + + if ((rv = virLogDaemonPostExecRestart(state_file, + pid_file, + &pid_file_fd, + privileged)) < 0) { + ret = VIR_LOG_DAEMON_ERR_INIT; + goto cleanup; + } + + /* rv == 1, means we setup everything from saved state, + * so only (possibly) daemonize and setup stuff from + * scratch if rv == 0 + */ + if (rv == 0) { + if (godaemon) { + char ebuf[1024]; + + if (chdir("/") < 0) { + VIR_ERROR(_("cannot change to root directory: %s"), + virStrerror(errno, ebuf, sizeof(ebuf))); + goto cleanup; + } + + if ((statuswrite = virLogDaemonForkIntoBackground(argv[0])) < 0) { + VIR_ERROR(_("Failed to fork as daemon: %s"), + virStrerror(errno, ebuf, sizeof(ebuf))); + goto cleanup; + } + } + + /* If we have a pidfile set, claim it now, exiting if already taken */ + if ((pid_file_fd = virPidFileAcquirePath(pid_file, false, getpid())) < 0) { + ret = VIR_LOG_DAEMON_ERR_PIDFILE; + goto cleanup; + } + + if (!(logDaemon = virLogDaemonNew(config, privileged))) { + ret = VIR_LOG_DAEMON_ERR_INIT; + goto cleanup; + } + + if ((rv = virLogDaemonSetupNetworkingSystemD(logDaemon->srv)) < 0) { + ret = VIR_LOG_DAEMON_ERR_NETWORK; + goto cleanup; + } + + /* Only do this, if systemd did not pass a FD */ + if (rv == 0 && + virLogDaemonSetupNetworkingNative(logDaemon->srv, sock_file) < 0) { + ret = VIR_LOG_DAEMON_ERR_NETWORK; + goto cleanup; + } + } + + if (timeout != -1) { + VIR_DEBUG("Registering shutdown timeout %d", timeout); + virNetDaemonAutoShutdown(logDaemon->dmn, + timeout); + } + + if ((virLogDaemonSetupSignals(logDaemon->dmn)) < 0) { + ret = VIR_LOG_DAEMON_ERR_SIGNAL; + goto cleanup; + } + + if (!(logProgram = virNetServerProgramNew(VIR_LOG_MANAGER_PROTOCOL_PROGRAM, + VIR_LOG_MANAGER_PROTOCOL_PROGRAM_VERSION, + virLogManagerProtocolProcs, + virLogManagerProtocolNProcs))) { + ret = VIR_LOG_DAEMON_ERR_INIT; + goto cleanup; + } + if (virNetServerAddProgram(logDaemon->srv, logProgram) < 0) { + ret = VIR_LOG_DAEMON_ERR_INIT; + goto cleanup; + } + + /* Disable error func, now logging is setup */ + virSetErrorFunc(NULL, virLogDaemonErrorHandler); + + + /* Tell parent of daemon that basic initialization is complete + * In particular we're ready to accept net connections & have + * written the pidfile + */ + if (statuswrite != -1) { + char status = 0; + while (write(statuswrite, &status, 1) == -1 && + errno == EINTR) + ; + VIR_FORCE_CLOSE(statuswrite); + } + + /* Start accepting new clients from network */ + + virNetServerUpdateServices(logDaemon->srv, true); + virNetDaemonRun(logDaemon->dmn); + + if (execRestart && + virLogDaemonPreExecRestart(state_file, + logDaemon->dmn, + argv) < 0) + ret = VIR_LOG_DAEMON_ERR_REEXEC; + else + ret = 0; + + cleanup: + virObjectUnref(logProgram); + virLogDaemonFree(logDaemon); + if (statuswrite != -1) { + if (ret != 0) { + /* Tell parent of daemon what failed */ + char status = ret; + while (write(statuswrite, &status, 1) == -1 && + errno == EINTR) + ; + } + VIR_FORCE_CLOSE(statuswrite); + } + if (pid_file_fd != -1) + virPidFileReleasePath(pid_file, pid_file_fd); + VIR_FREE(pid_file); + VIR_FREE(sock_file); + VIR_FREE(state_file); + VIR_FREE(run_dir); + return ret; + + no_memory: + VIR_ERROR(_("Can't allocate memory")); + exit(EXIT_FAILURE); +} diff --git a/src/logging/log_daemon.h b/src/logging/log_daemon.h new file mode 100644 index 0000000..a153160 --- /dev/null +++ b/src/logging/log_daemon.h @@ -0,0 +1,42 @@ +/* + * log_daemon.h: log management daemon + * + * Copyright (C) 2006-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@redhat.com> + */ + +#ifndef __VIR_LOG_DAEMON_H__ +# define __VIR_LOG_DAEMON_H__ + +# include "virthread.h" + +typedef struct _virLogDaemon virLogDaemon; +typedef virLogDaemon *virLogDaemonPtr; + +typedef struct _virLogDaemonClient virLogDaemonClient; +typedef virLogDaemonClient *virLogDaemonClientPtr; + +struct _virLogDaemonClient { + virMutex lock; + + pid_t clientPid; +}; + +extern virLogDaemonPtr logDaemon; + +#endif /* __VIR_LOG_DAEMON_H__ */ diff --git a/src/logging/log_daemon_config.c b/src/logging/log_daemon_config.c new file mode 100644 index 0000000..98d4c89 --- /dev/null +++ b/src/logging/log_daemon_config.c @@ -0,0 +1,203 @@ +/* + * log_daemon_config.c: virtlogd config file handling + * + * Copyright (C) 2006-2015 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * 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@redhat.com> + */ + +#include <config.h> + +#include "log_daemon_config.h" +#include "virconf.h" +#include "viralloc.h" +#include "virerror.h" +#include "virlog.h" +#include "rpc/virnetserver.h" +#include "configmake.h" +#include "virstring.h" +#include "virutil.h" + +#define VIR_FROM_THIS VIR_FROM_CONF + +VIR_LOG_INIT("logging.log_daemon_config"); + + +/* A helper function used by each of the following macros. */ +static int +checkType(virConfValuePtr p, const char *filename, + const char *key, virConfType required_type) +{ + if (p->type != required_type) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("remoteReadConfigFile: %s: %s: invalid type:" + " got %s; expected %s"), filename, key, + virConfTypeToString(p->type), + virConfTypeToString(required_type)); + return -1; + } + return 0; +} + +/* If there is no config data for the key, #var_name, then do nothing. + If there is valid data of type VIR_CONF_STRING, and VIR_STRDUP succeeds, + store the result in var_name. Otherwise, (i.e. invalid type, or VIR_STRDUP + failure), give a diagnostic and "goto" the cleanup-and-fail label. */ +#define GET_CONF_STR(conf, filename, var_name) \ + do { \ + virConfValuePtr p = virConfGetValue(conf, #var_name); \ + if (p) { \ + if (checkType(p, filename, #var_name, VIR_CONF_STRING) < 0) \ + goto error; \ + VIR_FREE(data->var_name); \ + if (VIR_STRDUP(data->var_name, p->str) < 0) \ + goto error; \ + } \ + } while (0) + +/* Like GET_CONF_STR, but for signed integer values. */ +#define GET_CONF_INT(conf, filename, var_name) \ + do { \ + virConfValuePtr p = virConfGetValue(conf, #var_name); \ + if (p) { \ + if (p->type != VIR_CONF_ULONG && \ + checkType(p, filename, #var_name, VIR_CONF_LONG) < 0) \ + goto error; \ + data->var_name = p->l; \ + } \ + } while (0) + +/* Like GET_CONF_STR, but for unsigned integer values. */ +#define GET_CONF_UINT(conf, filename, var_name) \ + do { \ + virConfValuePtr p = virConfGetValue(conf, #var_name); \ + if (p) { \ + if (checkType(p, filename, #var_name, VIR_CONF_ULONG) < 0) \ + goto error; \ + data->var_name = p->l; \ + } \ + } while (0) + +int +virLogDaemonConfigFilePath(bool privileged, char **configfile) +{ + if (privileged) { + if (VIR_STRDUP(*configfile, SYSCONFDIR "/libvirt/virtlogd.conf") < 0) + goto error; + } else { + char *configdir = NULL; + + if (!(configdir = virGetUserConfigDirectory())) + goto error; + + if (virAsprintf(configfile, "%s/virtlogd.conf", configdir) < 0) { + VIR_FREE(configdir); + goto error; + } + VIR_FREE(configdir); + } + + return 0; + + error: + return -1; +} + + +virLogDaemonConfigPtr +virLogDaemonConfigNew(bool privileged ATTRIBUTE_UNUSED) +{ + virLogDaemonConfigPtr data; + + if (VIR_ALLOC(data) < 0) + return NULL; + + data->max_clients = 1024; + + return data; +} + +void +virLogDaemonConfigFree(virLogDaemonConfigPtr data) +{ + if (!data) + return; + + VIR_FREE(data->log_filters); + VIR_FREE(data->log_outputs); + + VIR_FREE(data); +} + +static int +virLogDaemonConfigLoadOptions(virLogDaemonConfigPtr data, + const char *filename, + virConfPtr conf) +{ + GET_CONF_UINT(conf, filename, log_level); + GET_CONF_STR(conf, filename, log_filters); + GET_CONF_STR(conf, filename, log_outputs); + GET_CONF_UINT(conf, filename, max_clients); + + return 0; + + error: + return -1; +} + + +/* Read the config file if it exists. + * Only used in the remote case, hence the name. + */ +int +virLogDaemonConfigLoadFile(virLogDaemonConfigPtr data, + const char *filename, + bool allow_missing) +{ + virConfPtr conf; + int ret; + + if (allow_missing && + access(filename, R_OK) == -1 && + errno == ENOENT) + return 0; + + conf = virConfReadFile(filename, 0); + if (!conf) + return -1; + + ret = virLogDaemonConfigLoadOptions(data, filename, conf); + virConfFree(conf); + return ret; +} + +int virLogDaemonConfigLoadData(virLogDaemonConfigPtr data, + const char *filename, + const char *filedata) +{ + virConfPtr conf; + int ret; + + conf = virConfReadMem(filedata, strlen(filedata), 0); + if (!conf) + return -1; + + ret = virLogDaemonConfigLoadOptions(data, filename, conf); + virConfFree(conf); + return ret; +} diff --git a/src/logging/log_daemon_config.h b/src/logging/log_daemon_config.h new file mode 100644 index 0000000..24cc631 --- /dev/null +++ b/src/logging/log_daemon_config.h @@ -0,0 +1,50 @@ +/* + * log_daemon_config.h: virtlogd config file handling + * + * Copyright (C) 2006-2015 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * 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@redhat.com> + */ + +#ifndef __VIR_LOG_DAEMON_CONFIG_H__ +# define __VIR_LOG_DAEMON_CONFIG_H__ + +# include "internal.h" + +typedef struct _virLogDaemonConfig virLogDaemonConfig; +typedef virLogDaemonConfig *virLogDaemonConfigPtr; + +struct _virLogDaemonConfig { + int log_level; + char *log_filters; + char *log_outputs; + int max_clients; +}; + + +int virLogDaemonConfigFilePath(bool privileged, char **configfile); +virLogDaemonConfigPtr virLogDaemonConfigNew(bool privileged); +void virLogDaemonConfigFree(virLogDaemonConfigPtr data); +int virLogDaemonConfigLoadFile(virLogDaemonConfigPtr data, + const char *filename, + bool allow_missing); +int virLogDaemonConfigLoadData(virLogDaemonConfigPtr data, + const char *filename, + const char *filedata); + +#endif /* __LIBVIRTD_CONFIG_H__ */ diff --git a/src/logging/log_daemon_dispatch.c b/src/logging/log_daemon_dispatch.c new file mode 100644 index 0000000..98df178 --- /dev/null +++ b/src/logging/log_daemon_dispatch.c @@ -0,0 +1,37 @@ +/* + * log_daemon_dispatch.c: log management daemon dispatch + * + * Copyright (C) 2006-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@redhat.com> + */ + +#include <config.h> + +#include "rpc/virnetserver.h" +#include "rpc/virnetserverclient.h" +#include "virlog.h" +#include "virstring.h" +#include "log_daemon.h" +#include "log_protocol.h" +#include "virerror.h" + +#define VIR_FROM_THIS VIR_FROM_RPC + +VIR_LOG_INIT("logging.log_daemon_dispatch"); + +#include "log_daemon_dispatch_stubs.h" diff --git a/src/logging/log_daemon_dispatch.h b/src/logging/log_daemon_dispatch.h new file mode 100644 index 0000000..af3e3b4 --- /dev/null +++ b/src/logging/log_daemon_dispatch.h @@ -0,0 +1,31 @@ +/* + * log_daemon_dispatch.h: log management daemon dispatch + * + * Copyright (C) 2006-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@redhat.com> + */ + +#ifndef __VIR_LOG_DAEMON_DISPATCH_H__ +# define __VIR_LOG_DAEMON_DISPATCH_H__ + +# include "rpc/virnetserverprogram.h" + +extern virNetServerProgramProc virLogManagerProtocolProcs[]; +extern size_t virLogManagerProtocolNProcs; + +#endif /* __VIR_LOG_DAEMON_DISPATCH_H__ */ diff --git a/src/logging/log_protocol.x b/src/logging/log_protocol.x new file mode 100644 index 0000000..9b8fa41 --- /dev/null +++ b/src/logging/log_protocol.x @@ -0,0 +1,22 @@ +/* -*- c -*- + */ + +%#include "internal.h" + +typedef opaque virLogManagerProtocolUUID[VIR_UUID_BUFLEN]; + +/* Length of long, but not unbounded, strings. + * This is an arbitrary limit designed to stop the decoder from trying + * to allocate unbounded amounts of memory when fed with a bad message. + */ +const VIR_LOG_MANAGER_PROTOCOL_STRING_MAX = 65536; + +/* A long string, which may NOT be NULL. */ +typedef string virLogManagerProtocolNonNullString<VIR_LOG_MANAGER_PROTOCOL_STRING_MAX>; + +/* A long string, which may be NULL. */ +typedef virLogManagerProtocolNonNullString *virLogManagerProtocolString; + +/* Define the program number, protocol version and procedure numbers here. */ +const VIR_LOG_MANAGER_PROTOCOL_PROGRAM = 0x87539319; +const VIR_LOG_MANAGER_PROTOCOL_PROGRAM_VERSION = 1; diff --git a/src/logging/test_virtlogd.aug.in b/src/logging/test_virtlogd.aug.in new file mode 100644 index 0000000..cc659d2 --- /dev/null +++ b/src/logging/test_virtlogd.aug.in @@ -0,0 +1,12 @@ +module Test_virtlogd = + let conf = "log_level = 3 +log_filters=\"3:remote 4:event\" +log_outputs=\"3:syslog:libvirtd\" +log_buffer_size = 64 +" + + test Virtlogd.lns get conf = + { "log_level" = "3" } + { "log_filters" = "3:remote 4:event" } + { "log_outputs" = "3:syslog:libvirtd" } + { "log_buffer_size" = "64" } diff --git a/src/logging/virtlogd.aug b/src/logging/virtlogd.aug new file mode 100644 index 0000000..eefba5b --- /dev/null +++ b/src/logging/virtlogd.aug @@ -0,0 +1,45 @@ +(* /etc/libvirt/virtlogd.conf *) + +module Virtlogd = + autoload xfm + + let eol = del /[ \t]*\n/ "\n" + let value_sep = del /[ \t]*=[ \t]*/ " = " + let indent = del /[ \t]*/ "" + + let array_sep = del /,[ \t\n]*/ ", " + let array_start = del /\[[ \t\n]*/ "[ " + let array_end = del /\]/ "]" + + let str_val = del /\"/ "\"" . store /[^\"]*/ . del /\"/ "\"" + let bool_val = store /0|1/ + let int_val = store /[0-9]+/ + let str_array_element = [ seq "el" . str_val ] . del /[ \t\n]*/ "" + let str_array_val = counter "el" . array_start . ( str_array_element . ( array_sep . str_array_element ) * ) ? . array_end + + let str_entry (kw:string) = [ key kw . value_sep . str_val ] + let bool_entry (kw:string) = [ key kw . value_sep . bool_val ] + let int_entry (kw:string) = [ key kw . value_sep . int_val ] + let str_array_entry (kw:string) = [ key kw . value_sep . str_array_val ] + + + (* Config entry grouped by function - same order as example config *) + let logging_entry = int_entry "log_level" + | str_entry "log_filters" + | str_entry "log_outputs" + | int_entry "log_buffer_size" + | int_entry "max_clients" + + (* Each enty in the config is one of the following three ... *) + let entry = logging_entry + let comment = [ label "#comment" . del /#[ \t]*/ "# " . store /([^ \t\n][^\n]*)?/ . del /\n/ "\n" ] + let empty = [ label "#empty" . eol ] + + let record = indent . entry . eol + + let lns = ( record | comment | empty ) * + + let filter = incl "/etc/libvirt/virtlogd.conf" + . Util.stdexcl + + let xfm = transform lns filter diff --git a/src/logging/virtlogd.conf b/src/logging/virtlogd.conf new file mode 100644 index 0000000..609f67a --- /dev/null +++ b/src/logging/virtlogd.conf @@ -0,0 +1,67 @@ +# Master virtlogd daemon configuration file +# + +################################################################# +# +# Logging controls +# + +# Logging level: 4 errors, 3 warnings, 2 information, 1 debug +# basically 1 will log everything possible +#log_level = 3 + +# Logging filters: +# A filter allows to select a different logging level for a given category +# of logs +# The format for a filter is one of: +# x:name +# x:+name +# where name is a string which is matched against source file name, +# e.g., "remote", "qemu", or "util/json", the optional "+" prefix +# tells libvirt to log stack trace for each message matching name, +# and x is the minimal level where matching messages should be logged: +# 1: DEBUG +# 2: INFO +# 3: WARNING +# 4: ERROR +# +# Multiple filter can be defined in a single @filters, they just need to be +# separated by spaces. +# +# e.g. to only get warning or errors from the remote layer and only errors +# from the event layer: +#log_filters="3:remote 4:event" + +# Logging outputs: +# An output is one of the places to save logging information +# The format for an output can be: +# x:stderr +# output goes to stderr +# x:syslog:name +# use syslog for the output and use the given name as the ident +# x:file:file_path +# output to a file, with the given filepath +# In all case the x prefix is the minimal level, acting as a filter +# 1: DEBUG +# 2: INFO +# 3: WARNING +# 4: ERROR +# +# Multiple output can be defined, they just need to be separated by spaces. +# e.g. to log all warnings and errors to syslog under the virtlogd ident: +#log_outputs="3:syslog:virtlogd" +# + +# Log debug buffer size: +# +# This configuration option is no longer used, since the global +# log buffer functionality has been removed. Please configure +# suitable log_outputs/log_filters settings to obtain logs. +#log_buffer_size = 64 + +# The maximum number of concurrent client connections to allow +# over all sockets combined. +# Each running virtual machine will require one open connection +# to virtlogd. So 'max_clients' will affect how many VMs can +# be run on a host +#max_clients = 1024 diff --git a/src/logging/virtlogd.init.in b/src/logging/virtlogd.init.in new file mode 100644 index 0000000..1408236 --- /dev/null +++ b/src/logging/virtlogd.init.in @@ -0,0 +1,94 @@ +#!/bin/sh + +# the following is the LSB init header see +# http://www.linux-foundation.org/spec//booksets/LSB-Core-generic/LSB-Core-gen... +# +### BEGIN INIT INFO +# Provides: virtlogd +# Default-Start: +# Default-Stop: 0 1 2 3 4 5 6 +# Short-Description: virtual machine log manager +# Description: This is a daemon for managing logs +# of virtual machine consoles +### END INIT INFO + +# the following is chkconfig init header +# +# virtlogd: virtual machine log manager +# +# chkconfig: - 96 04 +# description: This is a daemon for managing logs \ +# of virtual machine consoles +# +# processname: virtlogd +# pidfile: @localstatedir@/run/virtlogd.pid +# + +# Source function library. +. @sysconfdir@/rc.d/init.d/functions + +SERVICE=virtlogd +PROCESS=virtlogd +PIDFILE=@localstatedir@/run/$SERVICE.pid + +VIRTLOGD_ARGS= + +test -f @sysconfdir@/sysconfig/virtlogd && . @sysconfdir@/sysconfig/virtlogd + +RETVAL=0 + +start() { + echo -n $"Starting $SERVICE daemon: " + daemon --pidfile $PIDFILE --check $SERVICE $PROCESS --daemon $VIRTLOGD_ARGS + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch @localstatedir@/log/subsys/$SERVICE +} + +stop() { + echo -n $"Stopping $SERVICE daemon: " + + killproc -p $PIDFILE $PROCESS + RETVAL=$? + echo + if [ $RETVAL -eq 0 ]; then + rm -f @localstatedir@/log/subsys/$SERVICE + rm -f $PIDFILE + fi +} + +restart() { + stop + start +} + +reload() { + echo -n $"Reloading $SERVICE configuration: " + + killproc -p $PIDFILE $PROCESS -USR1 + RETVAL=$? + echo + return $RETVAL +} + +# See how we were called. +case "$1" in + start|stop|restart|reload) + $1 + ;; + status) + status -p $PIDFILE $PROCESS + RETVAL=$? + ;; + force-reload) + reload + ;; + condrestart|try-restart) + [ -f @localstatedir@/log/subsys/$SERVICE ] && restart || : + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|condrestart|reload|force-reload|try-restart}" + exit 2 + ;; +esac +exit $RETVAL diff --git a/src/logging/virtlogd.pod.in b/src/logging/virtlogd.pod.in new file mode 100644 index 0000000..bba7714 --- /dev/null +++ b/src/logging/virtlogd.pod.in @@ -0,0 +1,162 @@ +=head1 NAME + +virtlogd - libvirt log management daemon + +=head1 SYNOPSIS + +B<virtlogd> [ -dv ] [ -f config_file ] [ -p pid_file ] + +B<virtlogd> --version + +=head1 DESCRIPTION + +The B<virtlogd> program is a server side daemon component of the libvirt +virtualization management system that is used to manage logs from virtual +machine consoles. + +This daemon is not used directly by libvirt client applications, rather it +is called on their behalf by B<libvirtd>. By maintaining the logs in a +standalone daemon, the main libvirtd daemon can be restarted without risk +of losing logs. The B<virtlogd> daemon has the ability to re-exec() +itself upon receiving SIGUSR1, to allow live upgrades without downtime. + +The virtlogd daemon listens for requests on a local Unix domain socket. + +=head1 OPTIONS + +=over + +=item B<-h, --help> + +Display command line help usage then exit. + +=item B<-d, --daemon> + +Run as a daemon and write PID file. + +=item B<-f, --config> I<FILE> + +Use this configuration file, overriding the default value. + +=item B<-p, --pid-file> I<FILE> + +Use this name for the PID file, overriding the default value. + +=item B<-v, --verbose> + +Enable output of verbose messages. + +=item B<-V, --version> + +Display version information then exit. + +=back + +=head1 SIGNALS + +On receipt of B<SIGUSR1> virtlogd will re-exec() its binary, while +maintaining all current logs and clients. This allows for live +upgrades of the virtlogd service. + +=head1 FILES + +=head2 When run as B<root>. + +=over + +=item F<SYSCONFDIR/virtlogd.conf> + +The default configuration file used by virtlogd, unless overridden on the +command line using the B<-f>|B<--config> option. + +=item F<LOCALSTATEDIR/run/libvirt/virtlogd-sock> + +The sockets libvirtd will use. + +=item F<LOCALSTATEDIR/run/virtlogd.pid> + +The PID file to use, unless overridden by the B<-p>|B<--pid-file> option. + +=back + +=head2 When run as B<non-root>. + +=over + +=item F<$XDG_CONFIG_HOME/virtlogd.conf> + +The default configuration file used by libvirtd, unless overridden on the +command line using the B<-f>|B<--config> option. + +=item F<$XDG_RUNTIME_DIR/libvirt/virtlogd-sock> + +The socket libvirtd will use. + +=item F<$XDG_RUNTIME_DIR/libvirt/virtlogd.pid> + +The PID file to use, unless overridden by the B<-p>|B<--pid-file> option. + +=item If $XDG_CONFIG_HOME is not set in your environment, libvirtd will use F<$HOME/.config> + +=item If $XDG_RUNTIME_DIR is not set in your environment, libvirtd will use F<$HOME/.cache> + +=back + +=head1 EXAMPLES + +To retrieve the version of virtlogd: + + # virtlogd --version + virtlogd (libvirt) 1.1.1 + # + +To start virtlogd, instructing it to daemonize and create a PID file: + + # virtlogd -d + # ls -la LOCALSTATEDIR/run/virtlogd.pid + -rw-r--r-- 1 root root 6 Jul 9 02:40 LOCALSTATEDIR/run/virtlogd.pid + # + +=head1 BUGS + +Please report all bugs you discover. This should be done via either: + +=over + +=item a) the mailing list + +L<http://libvirt.org/contact.html> + +=item or, + +B<> + +=item b) the bug tracker + +L<http://libvirt.org/bugs.html> + +=item Alternatively, you may report bugs to your software distributor / vendor. + +=back + +=head1 AUTHORS + +Please refer to the AUTHORS file distributed with libvirt. + +=head1 COPYRIGHT + +Copyright (C) 2006-2015 Red Hat, Inc., and the authors listed in the +libvirt AUTHORS file. + +=head1 LICENSE + +virtlogd is distributed under the terms of the GNU LGPL v2.1+. +This is free software; see the source for copying conditions. There +is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR +PURPOSE + +=head1 SEE ALSO + +L<libvirtd(8)>, L<http://www.libvirt.org/> + +=cut diff --git a/src/logging/virtlogd.service.in b/src/logging/virtlogd.service.in new file mode 100644 index 0000000..a264d3a --- /dev/null +++ b/src/logging/virtlogd.service.in @@ -0,0 +1,17 @@ +[Unit] +Description=Virtual machine log manager +Requires=virtlogd.socket +Documentation=man:virtlogd(8) +Documentation=http://libvirt.org + +[Service] +EnvironmentFile=-/etc/sysconfig/virtlogd +ExecStart=@sbindir@/virtlogd $VIRTLOGD_ARGS +ExecReload=/bin/kill -USR1 $MAINPID +# Loosing the logs is a really bad thing that will +# cause the machine to be fenced (rebooted), so make +# sure we discourage OOM killer +OOMScoreAdjust=-900 + +[Install] +Also=virtlogd.socket diff --git a/src/logging/virtlogd.socket.in b/src/logging/virtlogd.socket.in new file mode 100644 index 0000000..724976d --- /dev/null +++ b/src/logging/virtlogd.socket.in @@ -0,0 +1,8 @@ +[Unit] +Description=Virtual machine log manager socket + +[Socket] +ListenStream=@localstatedir@/run/libvirt/virtlogd-sock + +[Install] +WantedBy=sockets.target diff --git a/src/logging/virtlogd.sysconf b/src/logging/virtlogd.sysconf new file mode 100644 index 0000000..5886f35 --- /dev/null +++ b/src/logging/virtlogd.sysconf @@ -0,0 +1,3 @@ +# +# Pass extra arguments to virtlogd +#VIRTLOGD_ARGS= diff --git a/src/util/virerror.c b/src/util/virerror.c index 6dc05f4..098211a 100644 --- a/src/util/virerror.c +++ b/src/util/virerror.c @@ -134,6 +134,7 @@ VIR_ENUM_IMPL(virErrorDomain, VIR_ERR_DOMAIN_LAST, "Polkit", /* 60 */ "Thread jobs", "Admin Interface", + "Log Manager", ) -- 2.5.0

Define a new RPC protocol for the virtlogd daemon that provides for handling of logs. The initial RPC method defined allows a client to obtain a file handle to use for writing to a log file for a guest domain. The file handle passed back will not actually refer to the log file, but rather an anonymous pipe. The virtlogd daemon will forward I/O between them, ensuring file rotation happens when required. Initially the log setup is hardcoded to cap log files at 128 KB, and keep 2 backups when rolling over. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- po/POTFILES.in | 1 + src/Makefile.am | 4 + src/logging/log_daemon.c | 30 +++ src/logging/log_daemon.h | 3 + src/logging/log_daemon_dispatch.c | 32 ++- src/logging/log_handler.c | 429 ++++++++++++++++++++++++++++++++++++++ src/logging/log_handler.h | 46 ++++ src/logging/log_protocol.x | 49 +++++ 8 files changed, 593 insertions(+), 1 deletion(-) create mode 100644 src/logging/log_handler.c create mode 100644 src/logging/log_handler.h diff --git a/po/POTFILES.in b/po/POTFILES.in index 33bc258..55baaae 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -80,6 +80,7 @@ src/locking/lock_manager.c src/locking/sanlock_helper.c src/logging/log_daemon.c src/logging/log_daemon_config.c +src/logging/log_handler.c src/lxc/lxc_cgroup.c src/lxc/lxc_fuse.c src/lxc/lxc_hostdev.c diff --git a/src/Makefile.am b/src/Makefile.am index 9f80f2b..b305cab 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -268,6 +268,8 @@ LOG_PROTOCOL_GENERATED = \ logging/log_protocol.c \ $(NULL) +DRIVER_SOURCES += $(LOG_PROTOCOL_GENERATED) + LOG_PROTOCOL = $(srcdir)/logging/log_protocol.x EXTRA_DIST += $(LOG_PROTOCOL) \ $(LOG_PROTOCOL_GENERATED) @@ -289,6 +291,8 @@ LOG_DAEMON_SOURCES = \ logging/log_daemon_config.c \ logging/log_daemon_dispatch.c \ logging/log_daemon_dispatch.h \ + logging/log_handler.c \ + logging/log_handler.h \ $(NULL) logging/log_daemon_dispatch_stubs.h: $(LOG_PROTOCOL) \ diff --git a/src/logging/log_daemon.c b/src/logging/log_daemon.c index 184076c..bc13257 100644 --- a/src/logging/log_daemon.c +++ b/src/logging/log_daemon.c @@ -60,6 +60,7 @@ struct _virLogDaemon { virMutex lock; virNetDaemonPtr dmn; virNetServerPtr srv; + virLogHandlerPtr handler; }; virLogDaemonPtr logDaemon = NULL; @@ -114,6 +115,7 @@ virLogDaemonFree(virLogDaemonPtr logd) if (!logd) return; + virObjectUnref(logd->handler); virObjectUnref(logd->srv); virObjectUnref(logd->dmn); @@ -149,6 +151,9 @@ virLogDaemonNew(virLogDaemonConfigPtr config, bool privileged) virNetDaemonAddServer(logd->dmn, logd->srv) < 0) goto error; + if (!(logd->handler = virLogHandlerNew(privileged))) + goto error; + return logd; error: @@ -157,6 +162,12 @@ virLogDaemonNew(virLogDaemonConfigPtr config, bool privileged) } +virLogHandlerPtr virLogDaemonGetHandler(virLogDaemonPtr daemon) +{ + return daemon->handler; +} + + static virLogDaemonPtr virLogDaemonNewPostExecRestart(virJSONValuePtr object, bool privileged) { @@ -190,6 +201,16 @@ virLogDaemonNewPostExecRestart(virJSONValuePtr object, bool privileged) (void*)(intptr_t)(privileged ? 0x1 : 0x0)))) goto error; + if (!(child = virJSONValueObjectGet(object, "handler"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Malformed daemon data from JSON file")); + goto error; + } + + if (!(logd->handler = virLogHandlerNewPostExecRestart(child, + privileged))) + goto error; + return logd; error: @@ -778,6 +799,15 @@ virLogDaemonPreExecRestart(const char *state_file, goto cleanup; } + if (!(child = virLogHandlerPreExecRestart(logDaemon->handler))) + goto cleanup; + + if (virJSONValueObjectAppend(object, "handler", child) < 0) { + virJSONValueFree(child); + goto cleanup; + } + + if (!(state = virJSONValueToString(object, true))) goto cleanup; diff --git a/src/logging/log_daemon.h b/src/logging/log_daemon.h index a153160..b076a4f 100644 --- a/src/logging/log_daemon.h +++ b/src/logging/log_daemon.h @@ -24,6 +24,7 @@ # define __VIR_LOG_DAEMON_H__ # include "virthread.h" +# include "log_handler.h" typedef struct _virLogDaemon virLogDaemon; typedef virLogDaemon *virLogDaemonPtr; @@ -39,4 +40,6 @@ struct _virLogDaemonClient { extern virLogDaemonPtr logDaemon; +virLogHandlerPtr virLogDaemonGetHandler(virLogDaemonPtr daemon); + #endif /* __VIR_LOG_DAEMON_H__ */ diff --git a/src/logging/log_daemon_dispatch.c b/src/logging/log_daemon_dispatch.c index 98df178..d3464dd 100644 --- a/src/logging/log_daemon_dispatch.c +++ b/src/logging/log_daemon_dispatch.c @@ -1,7 +1,7 @@ /* * log_daemon_dispatch.c: log management daemon dispatch * - * Copyright (C) 2006-2015 Red Hat, Inc. + * 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 @@ -29,9 +29,39 @@ #include "log_daemon.h" #include "log_protocol.h" #include "virerror.h" +#include "virthreadjob.h" +#include "virfile.h" #define VIR_FROM_THIS VIR_FROM_RPC VIR_LOG_INIT("logging.log_daemon_dispatch"); #include "log_daemon_dispatch_stubs.h" + +static int +virLogManagerProtocolDispatchDomainOpenLogFile(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client ATTRIBUTE_UNUSED, + virNetMessagePtr msg, + virNetMessageErrorPtr rerr, + virLogManagerProtocolDomainOpenLogFileArgs *args) +{ + int fd = -1; + int ret = -1; + + if ((fd = virLogHandlerDomainOpenLogFile(virLogDaemonGetHandler(logDaemon), + args->driver, + (unsigned char *)args->dom.uuid, + args->dom.name)) < 0) + goto cleanup; + + if (virNetMessageAddFD(msg, fd) < 0) + goto cleanup; + + ret = 1; /* '1' tells caller we added some FDs */ + + cleanup: + VIR_FORCE_CLOSE(fd); + if (ret < 0) + virNetMessageSaveError(rerr); + return ret; +} diff --git a/src/logging/log_handler.c b/src/logging/log_handler.c new file mode 100644 index 0000000..b85f9e8 --- /dev/null +++ b/src/logging/log_handler.c @@ -0,0 +1,429 @@ +/* + * log_handler.c: log management daemon handler + * + * 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@redhat.com> + */ + +#include <config.h> + +#include "log_handler.h" +#include "virerror.h" +#include "virobject.h" +#include "virfile.h" +#include "viralloc.h" +#include "virstring.h" +#include "virlog.h" +#include "virrotatingfile.h" + +#include <unistd.h> +#include <fcntl.h> + +#include "configmake.h" + +VIR_LOG_INIT("logging.log_handler"); + +#define VIR_FROM_THIS VIR_FROM_LOGGING + +typedef struct _virLogHandlerLogFile virLogHandlerLogFile; +typedef virLogHandlerLogFile *virLogHandlerLogFilePtr; + +struct _virLogHandlerLogFile { + virRotatingFilePtr file; + int watch; + int pipefd; /* Read from QEMU via this */ +}; + +struct _virLogHandler { + virObjectLockable parent; + + bool privileged; + virLogHandlerLogFilePtr *files; + size_t nfiles; +}; + +static virClassPtr virLogHandlerClass; +static void virLogHandlerDispose(void *obj); + +static int virLogHandlerOnceInit(void) +{ + if (!(virLogHandlerClass = virClassNew(virClassForObjectLockable(), + "virLogHandler", + sizeof(virLogHandler), + virLogHandlerDispose))) + return -1; + + return 0; +} + +VIR_ONCE_GLOBAL_INIT(virLogHandler) + + + +static void virLogHandlerLogFileFree(virLogHandlerLogFilePtr file) +{ + if (!file) + return; + + VIR_FORCE_CLOSE(file->pipefd); + virRotatingFileFree(file->file); + + if (file->watch != -1) + virEventRemoveHandle(file->watch); + VIR_FREE(file); +} + + +static void virLogHandlerLogFileClose(virLogHandlerPtr handler, + virLogHandlerLogFilePtr file) +{ + size_t i; + + for (i = 0; i < handler->nfiles; i++) { + if (handler->files[i] == file) { + VIR_DELETE_ELEMENT(handler->files, i, handler->nfiles); + virLogHandlerLogFileFree(file); + break; + } + } +} + + +static virLogHandlerLogFilePtr +virLogHandlerGetLogFileFromWatch(virLogHandlerPtr handler, + int watch) +{ + size_t i; + + for (i = 0; i < handler->nfiles; i++) { + if (handler->files[i]->watch == watch) + return handler->files[i]; + } + + return NULL; +} + + +static void +virLogHandlerDomainLogFileEvent(int watch, + int fd, + int events, + void *opaque) +{ + virLogHandlerPtr handler = opaque; + virLogHandlerLogFilePtr logfile; + char buf[1024]; + ssize_t len; + + virObjectLock(handler); + logfile = virLogHandlerGetLogFileFromWatch(handler, watch); + if (!logfile || logfile->pipefd != fd) { + virEventRemoveHandle(watch); + return; + } + + reread: + len = read(fd, buf, sizeof(buf)); + if (len < 0) { + if (errno == EINTR) + goto reread; + + virReportSystemError(errno, "%s", + _("Unable to read from log pipe")); + goto error; + } + + if (virRotatingFileWrite(logfile->file, buf, len) != len) + goto error; + + if (events & VIR_EVENT_HANDLE_HANGUP) + goto error; + + virObjectUnlock(handler); + return; + + error: + virLogHandlerLogFileClose(handler, logfile); + virObjectUnlock(handler); +} + + +virLogHandlerPtr virLogHandlerNew(bool privileged) +{ + virLogHandlerPtr handler; + + if (virLogHandlerInitialize() < 0) + goto error; + + if (!(handler = virObjectLockableNew(virLogHandlerClass))) + goto error; + + handler->privileged = privileged; + + return handler; + + error: + return NULL; +} + + +static virLogHandlerLogFilePtr virLogHandlerLogFilePostExecRestart(virJSONValuePtr object) +{ + virLogHandlerLogFilePtr file; + const char *path; + + if (VIR_ALLOC(file) < 0) + return NULL; + + if ((path = virJSONValueObjectGetString(object, "path")) == NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing file path in JSON document")); + goto error; + } + + if ((file->file = virRotatingFileNew(path, 128 * 1024, 2, false, 0600)) == NULL) + goto error; + + if (virJSONValueObjectGetNumberInt(object, "pipefd", &file->pipefd) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing file pipefd in JSON document")); + goto error; + } + if (virSetInherit(file->pipefd, false) < 0) { + virReportSystemError(errno, "%s", + _("Cannot enable close-on-exec flag")); + goto error; + } + + return file; + + error: + virLogHandlerLogFileFree(file); + return NULL; +} + +virLogHandlerPtr virLogHandlerNewPostExecRestart(virJSONValuePtr object, + bool privileged) +{ + virLogHandlerPtr handler; + virJSONValuePtr files; + ssize_t n; + size_t i; + + if (!(handler = virLogHandlerNew(privileged))) + return NULL; + + if (!(files = virJSONValueObjectGet(object, "files"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing files data from JSON file")); + goto error; + } + + if ((n = virJSONValueArraySize(files)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Malformed files data from JSON file")); + goto error; + } + + for (i = 0; i < n; i++) { + virLogHandlerLogFilePtr file; + virJSONValuePtr child = virJSONValueArrayGet(files, i); + + if (!(file = virLogHandlerLogFilePostExecRestart(child))) + goto error; + + if (VIR_APPEND_ELEMENT_COPY(handler->files, handler->nfiles, file) < 0) + goto error; + + if ((file->watch = virEventAddHandle(file->pipefd, + VIR_EVENT_HANDLE_READABLE, + virLogHandlerDomainLogFileEvent, + handler, + NULL)) < 0) { + VIR_DELETE_ELEMENT(handler->files, handler->nfiles - 1, handler->nfiles); + goto error; + } + } + + + return handler; + + error: + virObjectUnref(handler); + return NULL; +} + + +static void virLogHandlerDispose(void *obj) +{ + virLogHandlerPtr handler = obj; + size_t i; + + for (i = 0; i < handler->nfiles; i++) + virLogHandlerLogFileFree(handler->files[i]); + VIR_FREE(handler->files); +} + + +static char * +virLogHandlerGetLogFilePathForDomain(virLogHandlerPtr handler, + const char *driver, + const unsigned char *domuuid ATTRIBUTE_UNUSED, + const char *domname) +{ + char *path; + if (handler->privileged) { + if (virAsprintf(&path, + LOCALSTATEDIR "/log/libvirt/%s/%s.log", + driver, domname) < 0) + return NULL; + } else { + char *cachedir; + + cachedir = virGetUserCacheDirectory(); + if (!cachedir) + return NULL; + + if (virAsprintf(&path, + "%s/%s/log/%s.log", cachedir, driver, domname) < 0) { + VIR_FREE(cachedir); + return NULL; + } + + } + return path; +} + +int virLogHandlerDomainOpenLogFile(virLogHandlerPtr handler, + const char *driver, + const unsigned char *domuuid ATTRIBUTE_UNUSED, + const char *domname) +{ + size_t i; + virLogHandlerLogFilePtr file = NULL; + int pipefd[2] = { -1, -1 }; + char *path; + + virObjectLock(handler); + + if (!(path = virLogHandlerGetLogFilePathForDomain(handler, + driver, + domuuid, + domname))) + goto error; + + for (i = 0; i < handler->nfiles; i++) { + if (STREQ(virRotatingFileGetPath(handler->files[i]->file), + path)) { + virReportSystemError(EBUSY, + _("Cannot open log file: '%s'"), + path); + goto error; + } + } + + if (pipe(pipefd) < 0) { + virReportSystemError(errno, "%s", + _("Cannot open fifo pipe")); + goto error; + } + if (VIR_ALLOC(file) < 0) + goto error; + + file->watch = -1; + file->pipefd = pipefd[0]; + pipefd[0] = -1; + + if ((file->file = virRotatingFileNew(path, 128 * 1024, 2, false, 0600)) == NULL) + goto error; + + if (VIR_APPEND_ELEMENT_COPY(handler->files, handler->nfiles, file) < 0) + goto error; + + if ((file->watch = virEventAddHandle(file->pipefd, + VIR_EVENT_HANDLE_READABLE, + virLogHandlerDomainLogFileEvent, + handler, + NULL)) < 0) { + VIR_DELETE_ELEMENT(handler->files, handler->nfiles - 1, handler->nfiles); + goto error; + } + + VIR_FREE(path); + + virObjectUnlock(handler); + return pipefd[1]; + + error: + VIR_FREE(path); + VIR_FORCE_CLOSE(pipefd[0]); + VIR_FORCE_CLOSE(pipefd[1]); + virLogHandlerLogFileFree(file); + virObjectUnlock(handler); + return -1; +} + + +virJSONValuePtr virLogHandlerPreExecRestart(virLogHandlerPtr handler) +{ + virJSONValuePtr ret = virJSONValueNewObject(); + virJSONValuePtr files; + size_t i; + + if (!ret) + return NULL; + + if (!(files = virJSONValueNewArray())) + goto error; + + if (virJSONValueObjectAppend(ret, "files", files) < 0) { + virJSONValueFree(files); + goto error; + } + + for (i = 0; i < handler->nfiles; i++) { + virJSONValuePtr file = virJSONValueNewObject(); + if (!file) + goto error; + + if (virJSONValueArrayAppend(files, file) < 0) { + virJSONValueFree(file); + goto error; + } + + if (virJSONValueObjectAppendNumberInt(file, "pipefd", + handler->files[i]->pipefd) < 0) + goto error; + + if (virJSONValueObjectAppendString(file, "path", + virRotatingFileGetPath(handler->files[i]->file)) < 0) + goto error; + + if (virSetInherit(handler->files[i]->pipefd, true) < 0) { + virReportSystemError(errno, "%s", + _("Cannot disable close-on-exec flag")); + goto error; + } + } + + return ret; + + error: + virJSONValueFree(ret); + return NULL; +} diff --git a/src/logging/log_handler.h b/src/logging/log_handler.h new file mode 100644 index 0000000..300a177 --- /dev/null +++ b/src/logging/log_handler.h @@ -0,0 +1,46 @@ +/* + * log_handler.h: log management daemon handler + * + * 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@redhat.com> + */ + +#ifndef __VIR_LOG_HANDLER_H__ +# define __VIR_LOG_HANDLER_H__ + +# include "internal.h" +# include "virjson.h" + +typedef struct _virLogHandler virLogHandler; +typedef virLogHandler *virLogHandlerPtr; + + +virLogHandlerPtr virLogHandlerNew(bool privileged); +virLogHandlerPtr virLogHandlerNewPostExecRestart(virJSONValuePtr child, + bool privileged); + +void virLogHandlerFree(virLogHandlerPtr handler); + +int virLogHandlerDomainOpenLogFile(virLogHandlerPtr handler, + const char *driver, + const unsigned char *domuuid, + const char *domname); + +virJSONValuePtr virLogHandlerPreExecRestart(virLogHandlerPtr handler); + +#endif /** __VIR_LOG_HANDLER_H__ */ diff --git a/src/logging/log_protocol.x b/src/logging/log_protocol.x index 9b8fa41..4fbcd41 100644 --- a/src/logging/log_protocol.x +++ b/src/logging/log_protocol.x @@ -17,6 +17,55 @@ typedef string virLogManagerProtocolNonNullString<VIR_LOG_MANAGER_PROTOCOL_STRIN /* A long string, which may be NULL. */ typedef virLogManagerProtocolNonNullString *virLogManagerProtocolString; +struct virLogManagerProtocolDomain { + virLogManagerProtocolUUID uuid; + virLogManagerProtocolNonNullString name; +}; +typedef struct virLogManagerProtocolDomain virLogManagerProtocolDomain; + +/* Obtain a file handle suitable for writing to a + * log file for a domain + */ +struct virLogManagerProtocolDomainOpenLogFileArgs { + virLogManagerProtocolNonNullString driver; + virLogManagerProtocolDomain dom; + unsigned int flags; +}; + /* Define the program number, protocol version and procedure numbers here. */ const VIR_LOG_MANAGER_PROTOCOL_PROGRAM = 0x87539319; const VIR_LOG_MANAGER_PROTOCOL_PROGRAM_VERSION = 1; + +enum virLogManagerProtocolProcedure { + /* Each function must be preceded by a comment providing one or + * more annotations: + * + * - @generate: none|client|server|both + * + * Whether to generate the dispatch stubs for the server + * and/or client code. + * + * - @readstream: paramnumber + * - @writestream: paramnumber + * + * The @readstream or @writestream annotations let daemon and src/remote + * create a stream. The direction is defined from the src/remote point + * of view. A readstream transfers data from daemon to src/remote. The + * <paramnumber> specifies at which offset the stream parameter is inserted + * in the function parameter list. + * + * - @priority: low|high + * + * Each API that might eventually access hypervisor's monitor (and thus + * block) MUST fall into low priority. However, there are some exceptions + * to this rule, e.g. domainDestroy. Other APIs MAY be marked as high + * priority. If in doubt, it's safe to choose low. Low is taken as default, + * and thus can be left out. + */ + + /** + * @generate: none + * @acl: none + */ + VIR_LOG_MANAGER_PROTOCOL_PROC_DOMAIN_OPEN_LOG_FILE = 1 +}; -- 2.5.0

Add the virLogManager API which allows for communication with the virtlogd daemon to RPC program. This provides the client side API to open log files for guest domains. The virtlogd daemon is setup to auto-spawn on first use when running unprivileged. For privileged usage, systemd socket activation is used instead. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- po/POTFILES.in | 1 + src/Makefile.am | 4 +- src/libvirt_private.syms | 6 ++ src/logging/log_manager.c | 197 ++++++++++++++++++++++++++++++++++++++++++++++ src/logging/log_manager.h | 42 ++++++++++ 5 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 src/logging/log_manager.c create mode 100644 src/logging/log_manager.h diff --git a/po/POTFILES.in b/po/POTFILES.in index 55baaae..91b88cb 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -81,6 +81,7 @@ src/locking/sanlock_helper.c src/logging/log_daemon.c src/logging/log_daemon_config.c src/logging/log_handler.c +src/logging/log_manager.c src/lxc/lxc_cgroup.c src/lxc/lxc_fuse.c src/lxc/lxc_hostdev.c diff --git a/src/Makefile.am b/src/Makefile.am index b305cab..aef4851 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -216,7 +216,9 @@ DRIVER_SOURCES = \ locking/lock_manager.c locking/lock_manager.h \ locking/lock_driver.h \ locking/lock_driver_nop.h locking/lock_driver_nop.c \ - locking/domain_lock.h locking/domain_lock.c + locking/domain_lock.h locking/domain_lock.c \ + logging/log_manager.c logging/log_manager.h \ + $(NULL) LOCK_DRIVER_SANLOCK_SOURCES = \ locking/lock_driver_sanlock.c diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index ff4b0e4..3d8e352 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1003,6 +1003,12 @@ virLockManagerPluginUsesState; virLockManagerRelease; +# logging/log_manager.h +virLogManagerNew; +virLogManagerFree; +virLogManagerDomainWriteLogFile; + + # nodeinfo.h nodeAllocPages; nodeCapsInitNUMA; diff --git a/src/logging/log_manager.c b/src/logging/log_manager.c new file mode 100644 index 0000000..d9d9497 --- /dev/null +++ b/src/logging/log_manager.c @@ -0,0 +1,197 @@ +/* + * log_manager.c: log management client + * + * 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@redhat.com> + */ + +#include <config.h> + +#include "configmake.h" + +#include "log_manager.h" +#include "log_protocol.h" +#include "viralloc.h" +#include "virutil.h" +#include "virstring.h" +#include "virerror.h" +#include "virfile.h" + +#include "rpc/virnetclient.h" + +#define VIR_FROM_THIS VIR_FROM_LOGGING + +struct _virLogManager { + virNetClientPtr client; + virNetClientProgramPtr program; + unsigned int serial; +}; + + +static char *virLogManagerDaemonPath(bool privileged) +{ + char *path; + if (privileged) { + if (VIR_STRDUP(path, LOCALSTATEDIR "/run/libvirt/virtlogd-sock") < 0) + return NULL; + } else { + char *rundir = NULL; + + if (!(rundir = virGetUserRuntimeDirectory())) + return NULL; + + if (virAsprintf(&path, "%s/virtlogd-sock", rundir) < 0) { + VIR_FREE(rundir); + return NULL; + } + + } + return path; +} + + +static virNetClientPtr +virLogManagerConnect(bool privileged, + virNetClientProgramPtr *prog) +{ + virNetClientPtr client = NULL; + char *logdpath; + char *daemonPath = NULL; + + *prog = NULL; + + if (!(logdpath = virLogManagerDaemonPath(privileged))) + goto error; + + if (!privileged && + !(daemonPath = virFileFindResourceFull("virtlogd", + NULL, NULL, + abs_topbuilddir "/src", + SBINDIR, + "VIRTLOGD_PATH"))) + goto error; + + if (!(client = virNetClientNewUNIX(logdpath, + daemonPath != NULL, + daemonPath))) + goto error; + + if (!(*prog = virNetClientProgramNew(VIR_LOG_MANAGER_PROTOCOL_PROGRAM, + VIR_LOG_MANAGER_PROTOCOL_PROGRAM_VERSION, + NULL, + 0, + NULL))) + goto error; + + if (virNetClientAddProgram(client, *prog) < 0) + goto error; + + VIR_FREE(daemonPath); + VIR_FREE(logdpath); + + return client; + + error: + VIR_FREE(daemonPath); + VIR_FREE(logdpath); + virNetClientClose(client); + virObjectUnref(client); + virObjectUnref(*prog); + return NULL; +} + + +virLogManagerPtr virLogManagerNew(bool privileged) +{ + virLogManagerPtr mgr; + + if (VIR_ALLOC(mgr) < 0) + goto error; + + if (!(mgr->client = virLogManagerConnect(privileged, &mgr->program))) + goto error; + + return mgr; + + error: + virLogManagerFree(mgr); + return NULL; +} + + +void virLogManagerFree(virLogManagerPtr mgr) +{ + if (!mgr) + return; + + if (mgr->client) + virNetClientClose(mgr->client); + virObjectUnref(mgr->client); + + VIR_FREE(mgr); +} + + +int virLogManagerDomainOpenLogFile(virLogManagerPtr mgr, + const char *driver, + const unsigned char *domuuid, + const char *domname, + unsigned int flags) +{ + struct virLogManagerProtocolDomainOpenLogFileArgs args; + int *fdout = NULL; + size_t fdoutlen = 0; + int ret = -1; + + memset(&args, 0, sizeof(args)); + + args.driver = (char *)driver; + memcpy(args.dom.uuid, domuuid, VIR_UUID_BUFLEN); + args.dom.name = (char *)domname; + args.flags = flags; + + if (virNetClientProgramCall(mgr->program, + mgr->client, + mgr->serial++, + VIR_LOG_MANAGER_PROTOCOL_PROC_DOMAIN_OPEN_LOG_FILE, + 0, NULL, &fdoutlen, &fdout, + (xdrproc_t)xdr_virLogManagerProtocolDomainOpenLogFileArgs, &args, + (xdrproc_t)xdr_void, NULL) < 0) + goto cleanup; + + if (fdoutlen != 1) { + if (fdoutlen) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("too many file descriptors received")); + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("no file descriptor received")); + } + goto cleanup; + } + + ret = fdout[0]; + cleanup: + if (ret < 0) { + while (fdoutlen) + VIR_FORCE_CLOSE(fdout[--fdoutlen]); + } + VIR_FREE(fdout); + + return ret; +} diff --git a/src/logging/log_manager.h b/src/logging/log_manager.h new file mode 100644 index 0000000..1403547 --- /dev/null +++ b/src/logging/log_manager.h @@ -0,0 +1,42 @@ +/* + * log_manager.h: log management client + * + * 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@redhat.com> + */ + + +#ifndef __VIR_LOG_MANAGER_H__ +# define __VIR_LOG_MANAGER_H__ + +# include "internal.h" + +typedef struct _virLogManager virLogManager; +typedef virLogManager *virLogManagerPtr; + +virLogManagerPtr virLogManagerNew(bool privileged); + +void virLogManagerFree(virLogManagerPtr mgr); + +int virLogManagerDomainOpenLogFile(virLogManagerPtr mgr, + const char *driver, + const unsigned char *domuuid, + const char *domname, + unsigned int flags); + +#endif /* __VIR_LOG_MANAGER_H__ */ -- 2.5.0

Currently the QEMU stdout/stderr streams are written directly to a regular file (eg /var/log/libvirt/qemu/$GUEST.log). While those can be rotated by logrotate (using copytruncate option) this is not very efficient. It also leaves open a window of opportunity for a compromised/broken QEMU to DOS the host filesystem by writing lots of text to stdout/stderr. This makes it possible to connect the stdout/stderr file handles to a pipe that is provided by virtlogd. The virtlogd daemon will read from this pipe and write data to the log file, performing file rotation whenever a pre-determined size limit is reached. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- cfg.mk | 2 +- src/qemu/libvirtd_qemu.aug | 1 + src/qemu/qemu.conf | 15 +++++++++++++ src/qemu/qemu_conf.c | 18 ++++++++++++++++ src/qemu/qemu_conf.h | 1 + src/qemu/qemu_domain.c | 43 +++++++++++++++++++++++++++++++------- src/qemu/qemu_process.c | 42 +++++++++++++++++++++---------------- src/qemu/test_libvirtd_qemu.aug.in | 1 + 8 files changed, 96 insertions(+), 27 deletions(-) diff --git a/cfg.mk b/cfg.mk index cf18a84..f3f43f2 100644 --- a/cfg.mk +++ b/cfg.mk @@ -775,7 +775,7 @@ sc_prohibit_gettext_markup: # lower-level code must not include higher-level headers. cross_dirs=$(patsubst $(srcdir)/src/%.,%,$(wildcard $(srcdir)/src/*/.)) cross_dirs_re=($(subst / ,/|,$(cross_dirs))) -mid_dirs=access|conf|cpu|locking|network|node_device|rpc|security|storage +mid_dirs=access|conf|cpu|locking|logging|network|node_device|rpc|security|storage sc_prohibit_cross_inclusion: @for dir in $(cross_dirs); do \ case $$dir in \ diff --git a/src/qemu/libvirtd_qemu.aug b/src/qemu/libvirtd_qemu.aug index 62951da..b6f6dc4 100644 --- a/src/qemu/libvirtd_qemu.aug +++ b/src/qemu/libvirtd_qemu.aug @@ -71,6 +71,7 @@ module Libvirtd_qemu = | bool_entry "set_process_name" | int_entry "max_processes" | int_entry "max_files" + | str_entry "stdio_handler" let device_entry = bool_entry "mac_filter" | bool_entry "relaxed_acs_check" diff --git a/src/qemu/qemu.conf b/src/qemu/qemu.conf index 1c589a2..084323f 100644 --- a/src/qemu/qemu.conf +++ b/src/qemu/qemu.conf @@ -515,3 +515,18 @@ # "/usr/share/OVMF/OVMF_CODE.fd:/usr/share/OVMF/OVMF_VARS.fd", # "/usr/share/AAVMF/AAVMF_CODE.fd:/usr/share/AAVMF/AAVMF_VARS.fd" #] + +# The backend to use for handling stdout/stderr output from +# QEMU processes. +# +# 'file': QEMU writes directly to a plain file. This is the +# historical default, but allows QEMU to inflict a +# denial of service attack on the host by exhausting +# filesystem space +# +# 'logd': QEMU writes to a pipe provided by virtlogd daemon. +# This is the current default, providing protection +# against denial of service by performing log file +# rollover when a size limit is hit. +# +#stdio_handler = logd diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 658a50e..14683f5 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -454,6 +454,7 @@ int virQEMUDriverConfigLoadFile(virQEMUDriverConfigPtr cfg, virConfValuePtr p; int ret = -1; size_t i; + char *stdioHandler = NULL; /* Just check the file is readable before opening it, otherwise * libvirt emits an error. @@ -781,6 +782,23 @@ int virQEMUDriverConfigLoadFile(virQEMUDriverConfigPtr cfg, GET_VALUE_ULONG("max_files", cfg->maxFiles); GET_VALUE_STR("lock_manager", cfg->lockManagerName); + GET_VALUE_STR("stdio_handler", stdioHandler); + if (stdioHandler) { + if (STREQ(stdioHandler, "logd")) { + cfg->stdioLogD = true; + } else if (STREQ(stdioHandler, "file")) { + cfg->stdioLogD = false; + } else { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Unknown stdio handler %s"), + stdioHandler); + VIR_FREE(stdioHandler); + goto cleanup; + } + VIR_FREE(stdioHandler); + } else { + cfg->stdioLogD = true; + } GET_VALUE_ULONG("max_queued", cfg->maxQueuedJobs); diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index ed9cd46..4b33770 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -173,6 +173,7 @@ struct _virQEMUDriverConfig { int migrationPortMax; bool logTimestamp; + bool stdioLogD; /* Pairs of loader:nvram paths. The list is @nloader items long */ char **loader; diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 890d8ed..c9dbdde 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -40,6 +40,7 @@ #include "virstoragefile.h" #include "virstring.h" #include "virthreadjob.h" +#include "logging/log_manager.h" #include "storage/storage_driver.h" @@ -2275,22 +2276,48 @@ qemuDomainOpenLogHelper(virQEMUDriverConfigPtr cfg, } +static int +qemuDomainCreateLogdFile(virQEMUDriverPtr driver, + virDomainObjPtr vm) +{ + int fd = -1; + virLogManagerPtr mgr = NULL; + + if (!(mgr = virLogManagerNew(driver->privileged))) + goto cleanup; + + fd = virLogManagerDomainOpenLogFile(mgr, + "qemu", + vm->def->uuid, + vm->def->name, 0); + + cleanup: + virLogManagerFree(mgr); + return fd; +} + + int qemuDomainCreateLog(virQEMUDriverPtr driver, virDomainObjPtr vm, bool append) { virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); - int oflags; int ret; - oflags = O_CREAT | O_WRONLY; - /* Only logrotate files in /var/log, so only append if running privileged */ - if (virQEMUDriverIsPrivileged(driver) || append) - oflags |= O_APPEND; - else - oflags |= O_TRUNC; + if (cfg->stdioLogD) { + /* virtlogd always operates in append mode */ + ret = qemuDomainCreateLogdFile(driver, vm); + } else { + int oflags; + oflags = O_CREAT | O_WRONLY; + /* Only logrotate files in /var/log, so only append if running privileged */ + if (virQEMUDriverIsPrivileged(driver) || append) + oflags |= O_APPEND; + else + oflags |= O_TRUNC; - ret = qemuDomainOpenLogHelper(cfg, vm, oflags, S_IRUSR | S_IWUSR); + ret = qemuDomainOpenLogHelper(cfg, vm, oflags, S_IRUSR | S_IWUSR); + } virObjectUnref(cfg); return ret; } diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index f744419..e72ca20 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -4774,7 +4774,10 @@ int qemuProcessStart(virConnectPtr conn, qemuDomainObjCheckTaint(driver, vm, logfile); - if ((pos = lseek(logfile, 0, SEEK_END)) < 0) + /* When using logd, the logfile FD is a pipe which is + * not seekable... */ + if (!cfg->stdioLogD && + (pos = lseek(logfile, 0, SEEK_END)) < 0) VIR_WARN("Unable to seek to end of logfile: %s", virStrerror(errno, ebuf, sizeof(ebuf))); @@ -5201,26 +5204,29 @@ void qemuProcessStop(virQEMUDriverPtr driver, /* Wake up anything waiting on domain condition */ virDomainObjBroadcast(vm); - if ((logfile = qemuDomainCreateLog(driver, vm, true)) < 0) { - /* To not break the normal domain shutdown process, skip the - * timestamp log writing if failed on opening log file. */ - VIR_WARN("Unable to open logfile: %s", - virStrerror(errno, ebuf, sizeof(ebuf))); - } else { - if ((timestamp = virTimeStringNow()) != NULL) { - if (safewrite(logfile, timestamp, strlen(timestamp)) < 0 || - safewrite(logfile, SHUTDOWN_POSTFIX, - strlen(SHUTDOWN_POSTFIX)) < 0) { - VIR_WARN("Unable to write timestamp to logfile: %s", - virStrerror(errno, ebuf, sizeof(ebuf))); + /* We let virtlogd put stop markers in */ + if (!cfg->stdioLogD) { + if ((logfile = qemuDomainCreateLog(driver, vm, true)) < 0) { + /* To not break the normal domain shutdown process, skip the + * timestamp log writing if failed on opening log file. */ + VIR_WARN("Unable to open logfile: %s", + virStrerror(errno, ebuf, sizeof(ebuf))); + } else { + if ((timestamp = virTimeStringNow()) != NULL) { + if (safewrite(logfile, timestamp, strlen(timestamp)) < 0 || + safewrite(logfile, SHUTDOWN_POSTFIX, + strlen(SHUTDOWN_POSTFIX)) < 0) { + VIR_WARN("Unable to write timestamp to logfile: %s", + virStrerror(errno, ebuf, sizeof(ebuf))); + } + + VIR_FREE(timestamp); } - VIR_FREE(timestamp); + if (VIR_CLOSE(logfile) < 0) + VIR_WARN("Unable to close logfile: %s", + virStrerror(errno, ebuf, sizeof(ebuf))); } - - if (VIR_CLOSE(logfile) < 0) - VIR_WARN("Unable to close logfile: %s", - virStrerror(errno, ebuf, sizeof(ebuf))); } /* Clear network bandwidth */ diff --git a/src/qemu/test_libvirtd_qemu.aug.in b/src/qemu/test_libvirtd_qemu.aug.in index fc4935b..8bec743 100644 --- a/src/qemu/test_libvirtd_qemu.aug.in +++ b/src/qemu/test_libvirtd_qemu.aug.in @@ -78,3 +78,4 @@ module Test_libvirtd_qemu = { "1" = "/usr/share/OVMF/OVMF_CODE.fd:/usr/share/OVMF/OVMF_VARS.fd" } { "2" = "/usr/share/AAVMF/AAVMF_CODE.fd:/usr/share/AAVMF/AAVMF_VARS.fd" } } +{ "stdio_handler" = "logd" } -- 2.5.0

On Tue, Nov 03, 2015 at 16:04:24 +0000, Daniel Berrange wrote:
Currently the QEMU stdout/stderr streams are written directly to a regular file (eg /var/log/libvirt/qemu/$GUEST.log). While those can be rotated by logrotate (using copytruncate option) this is not very efficient. It also leaves open a window of opportunity for a compromised/broken QEMU to DOS the host filesystem by writing lots of text to stdout/stderr.
This makes it possible to connect the stdout/stderr file handles to a pipe that is provided by virtlogd. The virtlogd daemon will read from this pipe and write data to the log file, performing file rotation whenever a pre-determined size limit is reached.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- cfg.mk | 2 +- src/qemu/libvirtd_qemu.aug | 1 + src/qemu/qemu.conf | 15 +++++++++++++ src/qemu/qemu_conf.c | 18 ++++++++++++++++ src/qemu/qemu_conf.h | 1 + src/qemu/qemu_domain.c | 43 +++++++++++++++++++++++++++++++------- src/qemu/qemu_process.c | 42 +++++++++++++++++++++---------------- src/qemu/test_libvirtd_qemu.aug.in | 1 + 8 files changed, 96 insertions(+), 27 deletions(-)
[...]
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index f744419..e72ca20 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -4774,7 +4774,10 @@ int qemuProcessStart(virConnectPtr conn,
qemuDomainObjCheckTaint(driver, vm, logfile);
- if ((pos = lseek(logfile, 0, SEEK_END)) < 0) + /* When using logd, the logfile FD is a pipe which is + * not seekable... */ + if (!cfg->stdioLogD && + (pos = lseek(logfile, 0, SEEK_END)) < 0) VIR_WARN("Unable to seek to end of logfile: %s", virStrerror(errno, ebuf, sizeof(ebuf)));
This will break the code that is in place to read the qemu log file in case of a early qemu startup failure so that we can report a semi-useful error message. Additionally in case where you don't use QMP and qemu does not support chardev info retrieval, this will also break the lookup of the PTYs for serials/parallels/channels. As a solution here I'd rather see that we drop support for such old qemus finally and not have to care about it any more. We probably need a set of APIs here, that will allow you to mark a place in the logfile and a second API that will allow to retrieve the data between the marker and the end. Peter

On Wed, Nov 04, 2015 at 09:17:00AM +0100, Peter Krempa wrote:
On Tue, Nov 03, 2015 at 16:04:24 +0000, Daniel Berrange wrote:
Currently the QEMU stdout/stderr streams are written directly to a regular file (eg /var/log/libvirt/qemu/$GUEST.log). While those can be rotated by logrotate (using copytruncate option) this is not very efficient. It also leaves open a window of opportunity for a compromised/broken QEMU to DOS the host filesystem by writing lots of text to stdout/stderr.
This makes it possible to connect the stdout/stderr file handles to a pipe that is provided by virtlogd. The virtlogd daemon will read from this pipe and write data to the log file, performing file rotation whenever a pre-determined size limit is reached.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- cfg.mk | 2 +- src/qemu/libvirtd_qemu.aug | 1 + src/qemu/qemu.conf | 15 +++++++++++++ src/qemu/qemu_conf.c | 18 ++++++++++++++++ src/qemu/qemu_conf.h | 1 + src/qemu/qemu_domain.c | 43 +++++++++++++++++++++++++++++++------- src/qemu/qemu_process.c | 42 +++++++++++++++++++++---------------- src/qemu/test_libvirtd_qemu.aug.in | 1 + 8 files changed, 96 insertions(+), 27 deletions(-)
[...]
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index f744419..e72ca20 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -4774,7 +4774,10 @@ int qemuProcessStart(virConnectPtr conn,
qemuDomainObjCheckTaint(driver, vm, logfile);
- if ((pos = lseek(logfile, 0, SEEK_END)) < 0) + /* When using logd, the logfile FD is a pipe which is + * not seekable... */ + if (!cfg->stdioLogD && + (pos = lseek(logfile, 0, SEEK_END)) < 0) VIR_WARN("Unable to seek to end of logfile: %s", virStrerror(errno, ebuf, sizeof(ebuf)));
This will break the code that is in place to read the qemu log file in case of a early qemu startup failure so that we can report a semi-useful error message.
Ohh, yes, I should have known this was useful for something really :-)
Additionally in case where you don't use QMP and qemu does not support chardev info retrieval, this will also break the lookup of the PTYs for serials/parallels/channels. As a solution here I'd rather see that we drop support for such old qemus finally and not have to care about it any more.
So this relies on the 'info chardev' command which was added in: commit 5ccfae10a79e52654c9edc68da6d05f29466b4f9 Author: aliguori <aliguori@c046a42c-6fe2-441c-8c8c-71466251a162> Date: Fri Oct 31 17:31:29 2008 +0000 Implement "info chardev" command. (Gerd Hoffmann) This is QEMU v1.0 or later. Given that it is 7 years old, I think we are justified in raising our min QEMU version to 1.0. There's probably a fair bit of other cruft we'll kill by doing this.
We probably need a set of APIs here, that will allow you to mark a place in the logfile and a second API that will allow to retrieve the data between the marker and the end.
Yeah, I'll investigate this. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On Thu, Nov 05, 2015 at 10:39:59AM +0000, Daniel P. Berrange wrote:
On Wed, Nov 04, 2015 at 09:17:00AM +0100, Peter Krempa wrote:
On Tue, Nov 03, 2015 at 16:04:24 +0000, Daniel Berrange wrote:
Currently the QEMU stdout/stderr streams are written directly to a regular file (eg /var/log/libvirt/qemu/$GUEST.log). While those can be rotated by logrotate (using copytruncate option) this is not very efficient. It also leaves open a window of opportunity for a compromised/broken QEMU to DOS the host filesystem by writing lots of text to stdout/stderr.
This makes it possible to connect the stdout/stderr file handles to a pipe that is provided by virtlogd. The virtlogd daemon will read from this pipe and write data to the log file, performing file rotation whenever a pre-determined size limit is reached.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- cfg.mk | 2 +- src/qemu/libvirtd_qemu.aug | 1 + src/qemu/qemu.conf | 15 +++++++++++++ src/qemu/qemu_conf.c | 18 ++++++++++++++++ src/qemu/qemu_conf.h | 1 + src/qemu/qemu_domain.c | 43 +++++++++++++++++++++++++++++++------- src/qemu/qemu_process.c | 42 +++++++++++++++++++++---------------- src/qemu/test_libvirtd_qemu.aug.in | 1 + 8 files changed, 96 insertions(+), 27 deletions(-)
[...]
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index f744419..e72ca20 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -4774,7 +4774,10 @@ int qemuProcessStart(virConnectPtr conn,
qemuDomainObjCheckTaint(driver, vm, logfile);
- if ((pos = lseek(logfile, 0, SEEK_END)) < 0) + /* When using logd, the logfile FD is a pipe which is + * not seekable... */ + if (!cfg->stdioLogD && + (pos = lseek(logfile, 0, SEEK_END)) < 0) VIR_WARN("Unable to seek to end of logfile: %s", virStrerror(errno, ebuf, sizeof(ebuf)));
This will break the code that is in place to read the qemu log file in case of a early qemu startup failure so that we can report a semi-useful error message.
Ohh, yes, I should have known this was useful for something really :-)
Additionally in case where you don't use QMP and qemu does not support chardev info retrieval, this will also break the lookup of the PTYs for serials/parallels/channels. As a solution here I'd rather see that we drop support for such old qemus finally and not have to care about it any more.
So this relies on the 'info chardev' command which was added in:
commit 5ccfae10a79e52654c9edc68da6d05f29466b4f9 Author: aliguori <aliguori@c046a42c-6fe2-441c-8c8c-71466251a162> Date: Fri Oct 31 17:31:29 2008 +0000
Implement "info chardev" command. (Gerd Hoffmann)
This is QEMU v1.0 or later. Given that it is 7 years old, I think we are justified in raising our min QEMU version to 1.0. There's probably a fair bit of other cruft we'll kill by doing this.
Opps, that should be 0.11 or later, *not* 1.0 or later. We don't want to raise min version beyond 0.12.0 as that's vintage that's still used in RHEL-6 era distros, which are reasonable to want to continue to support. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On Wed, Nov 04, 2015 at 09:17:00AM +0100, Peter Krempa wrote:
On Tue, Nov 03, 2015 at 16:04:24 +0000, Daniel Berrange wrote:
Currently the QEMU stdout/stderr streams are written directly to a regular file (eg /var/log/libvirt/qemu/$GUEST.log). While those can be rotated by logrotate (using copytruncate option) this is not very efficient. It also leaves open a window of opportunity for a compromised/broken QEMU to DOS the host filesystem by writing lots of text to stdout/stderr.
This makes it possible to connect the stdout/stderr file handles to a pipe that is provided by virtlogd. The virtlogd daemon will read from this pipe and write data to the log file, performing file rotation whenever a pre-determined size limit is reached.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- cfg.mk | 2 +- src/qemu/libvirtd_qemu.aug | 1 + src/qemu/qemu.conf | 15 +++++++++++++ src/qemu/qemu_conf.c | 18 ++++++++++++++++ src/qemu/qemu_conf.h | 1 + src/qemu/qemu_domain.c | 43 +++++++++++++++++++++++++++++++------- src/qemu/qemu_process.c | 42 +++++++++++++++++++++---------------- src/qemu/test_libvirtd_qemu.aug.in | 1 + 8 files changed, 96 insertions(+), 27 deletions(-)
[...]
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index f744419..e72ca20 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -4774,7 +4774,10 @@ int qemuProcessStart(virConnectPtr conn,
qemuDomainObjCheckTaint(driver, vm, logfile);
- if ((pos = lseek(logfile, 0, SEEK_END)) < 0) + /* When using logd, the logfile FD is a pipe which is + * not seekable... */ + if (!cfg->stdioLogD && + (pos = lseek(logfile, 0, SEEK_END)) < 0) VIR_WARN("Unable to seek to end of logfile: %s", virStrerror(errno, ebuf, sizeof(ebuf)));
This will break the code that is in place to read the qemu log file in case of a early qemu startup failure so that we can report a semi-useful error message.
Additionally in case where you don't use QMP and qemu does not support chardev info retrieval, this will also break the lookup of the PTYs for serials/parallels/channels. As a solution here I'd rather see that we drop support for such old qemus finally and not have to care about it any more.
We probably need a set of APIs here, that will allow you to mark a place in the logfile and a second API that will allow to retrieve the data between the marker and the end.
Well, solving all that was incredibly painful :-) After re-writing it 3 times, I've now got a satisfactory solution that refactors all this log handling code in the QEMU driver.... Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|
participants (2)
-
Daniel P. Berrange
-
Peter Krempa