[libvirt] [PATCH v2 00/13] Introduce a virtlogd daemon

This is a v2 of: https://www.redhat.com/archives/libvir-list/2015-November/msg00085.html 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. Changed in v2: - Expanded the virrotatingfile module to handle reading from files and querying position / seeking - Fixed rollover when no backups are enabled - Rollover early if we see a \n in the last 80 bytes - Totally refactor the QEMU code dealing with log files to remove all direct use of file handles and hide it in a qemuDomainLogContextPtr. This ensures we can fetch startup errors from virtlogd when needed Daniel P. Berrange (13): util: add APIs for reading/writing from/to rotating files Import stripped down virtlockd code as basis of virtlogd logging: introduce log handling protocol logging: add client for virtlogd daemon qemu: remove writing to QEMU log file for rename operation qemu: unify code for reporting errors from QEMU log files qemu: introduce a qemuDomainLogContext object qemu: convert log file creation to use qemuDomainLogContextPtr qemu: change qemuDomainTaint APIs to accept qemuDomainLogContextPtr qemu: convert qemuLogOperation to take a qemuDomainLogContextPtr qemu: convert process stop/attach to use qemuDomainLogContextPtr qemu: convert monitor to use qemuDomainLogContextPtr indirectly 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 | 6 + src/Makefile.am | 178 +++++- src/libvirt_private.syms | 21 + 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 | 143 +++++ src/logging/log_daemon_dispatch.h | 31 + src/logging/log_handler.c | 524 ++++++++++++++++ src/logging/log_handler.h | 63 ++ src/logging/log_manager.c | 283 +++++++++ src/logging/log_manager.h | 61 ++ src/logging/log_protocol.x | 115 ++++ 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 | 353 +++++++---- src/qemu/qemu_domain.h | 37 +- src/qemu/qemu_driver.c | 38 +- src/qemu/qemu_migration.c | 2 +- src/qemu/qemu_monitor.c | 89 +-- src/qemu/qemu_monitor.h | 8 +- src/qemu/qemu_process.c | 320 ++++------ src/qemu/qemu_process.h | 2 - src/qemu/test_libvirtd_qemu.aug.in | 1 + src/util/virerror.c | 1 + src/util/virrotatingfile.c | 608 ++++++++++++++++++ src/util/virrotatingfile.h | 62 ++ tests/Makefile.am | 6 + tests/virrotatingfiletest.c | 698 +++++++++++++++++++++ 44 files changed, 5180 insertions(+), 456 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 virRotatingFileReader and virRotatingFileWriter objects which allow reading & writing from/to files 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 | 13 + src/util/virrotatingfile.c | 608 ++++++++++++++++++++++++++++++++++++++ src/util/virrotatingfile.h | 62 ++++ tests/Makefile.am | 6 + tests/virrotatingfiletest.c | 698 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1389 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..f10493e 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2046,6 +2046,19 @@ virRandomGenerateWWN; virRandomInt; +# util/virrotatingfile.h +virRotatingFileReaderConsume; +virRotatingFileReaderFree; +virRotatingFileReaderNew; +virRotatingFileReaderSeek; +virRotatingFileWriterAppend; +virRotatingFileWriterFree; +virRotatingFileWriterGetINode; +virRotatingFileWriterGetOffset; +virRotatingFileWriterGetPath; +virRotatingFileWriterNew; + + # util/virscsi.h virSCSIDeviceFileIterate; virSCSIDeviceFree; diff --git a/src/util/virrotatingfile.c b/src/util/virrotatingfile.c new file mode 100644 index 0000000..840b55f --- /dev/null +++ b/src/util/virrotatingfile.c @@ -0,0 +1,608 @@ +/* + * 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 <sys/stat.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 + +typedef struct virRotatingFileWriterEntry virRotatingFileWriterEntry; +typedef virRotatingFileWriterEntry *virRotatingFileWriterEntryPtr; + +typedef struct virRotatingFileReaderEntry virRotatingFileReaderEntry; +typedef virRotatingFileReaderEntry *virRotatingFileReaderEntryPtr; + +struct virRotatingFileWriterEntry { + int fd; + off_t inode; + off_t pos; + off_t len; +}; + +struct virRotatingFileWriter { + char *basepath; + virRotatingFileWriterEntryPtr entry; + size_t maxbackup; + mode_t mode; + size_t maxlen; +}; + + +struct virRotatingFileReaderEntry { + char *path; + int fd; + off_t inode; +}; + +struct virRotatingFileReader { + virRotatingFileReaderEntryPtr *entries; + size_t nentries; + size_t current; +}; + + +static void virRotatingFileWriterEntryFree(virRotatingFileWriterEntryPtr entry) +{ + if (!entry) + return; + + VIR_FORCE_CLOSE(entry->fd); + VIR_FREE(entry); +} + + +static void virRotatingFileReaderEntryFree(virRotatingFileReaderEntryPtr entry) +{ + if (!entry) + return; + + VIR_FREE(entry->path); + VIR_FORCE_CLOSE(entry->fd); + VIR_FREE(entry); +} + + +static virRotatingFileWriterEntryPtr +virRotatingFileWriterEntryNew(const char *path, mode_t mode) +{ + virRotatingFileWriterEntryPtr entry; + struct stat sb; + + VIR_DEBUG("Opening %s mode=%02o", path, mode); + + if (VIR_ALLOC(entry) < 0) + return NULL; + + if ((entry->fd = open(path, O_CREAT|O_APPEND|O_WRONLY, mode)) < 0) { + virReportSystemError(errno, + _("Unable to open file: %s"), path); + goto error; + } + + entry->pos = lseek(entry->fd, 0, SEEK_END); + if (entry->pos == (off_t)-1) { + virReportSystemError(errno, + _("Unable to determine current file offset: %s"), + path); + goto error; + } + + if (fstat(entry->fd, &sb) < 0) { + virReportSystemError(errno, + _("Unable to determine current file inode: %s"), + path); + goto error; + } + + entry->len = sb.st_size; + entry->inode = sb.st_ino; + + return entry; + + error: + virRotatingFileWriterEntryFree(entry); + return NULL; +} + + +static virRotatingFileReaderEntryPtr +virRotatingFileReaderEntryNew(const char *path) +{ + virRotatingFileReaderEntryPtr entry; + struct stat sb; + + VIR_DEBUG("Opening %s", path); + + if (VIR_ALLOC(entry) < 0) + return NULL; + + if (VIR_STRDUP(entry->path, path) < 0) + goto error; + + if ((entry->fd = open(path, O_RDONLY)) < 0) { + if (errno != ENOENT) { + virReportSystemError(errno, + _("Unable to open file: %s"), path); + goto error; + } + } + + if (entry->fd != -1) { + if (fstat(entry->fd, &sb) < 0) { + virReportSystemError(errno, + _("Unable to determine current file inode: %s"), + path); + goto error; + } + + entry->inode = sb.st_ino; + } + + return entry; + + error: + virRotatingFileReaderEntryFree(entry); + return NULL; +} + + +static int virRotatingFileWriterDelete(virRotatingFileWriterPtr file) +{ + size_t i; + + if (unlink(file->basepath) < 0 && + errno != ENOENT) { + virReportSystemError(errno, + _("Unable to delete file %s"), + file->basepath); + return -1; + } + + for (i = 0; i < file->maxbackup; i++) { + char *oldpath; + if (virAsprintf(&oldpath, "%s.%zu", file->basepath, i) < 0) + return -1; + + if (unlink(oldpath) < 0 && + errno != ENOENT) { + virReportSystemError(errno, + _("Unable to delete file %s"), + oldpath); + VIR_FREE(oldpath); + return -1; + } + VIR_FREE(oldpath); + } + + return 0; +} + + +/** + * virRotatingFileWriterNew + * @path: the base path for files + * @maxlen: the maximum number of bytes to write before rollover + * @maxbackup: number of backup files to keep when rolloing over + * @truncate: whether to truncate the current files when opening + * @mode: the file mode to use for creating new files + * + * Create a new object for writing data to a file with + * automatic rollover. If @maxbackup is zero, no backup + * files will be created. The primary file will just get + * truncated and reopened. + * + * The files will never exceed @maxlen bytes in size, + * but may be rolled over before they reach this size + * in order to avoid splitting lines + */ +virRotatingFileWriterPtr virRotatingFileWriterNew(const char *path, + off_t maxlen, + size_t maxbackup, + bool truncate, + mode_t mode) +{ + virRotatingFileWriterPtr file; + + if (VIR_ALLOC(file) < 0) + goto error; + + if (VIR_STRDUP(file->basepath, path) < 0) + goto error; + + file->mode = mode; + file->maxbackup = maxbackup; + file->maxlen = maxlen; + + if (truncate && + virRotatingFileWriterDelete(file) < 0) + goto error; + + if (!(file->entry = virRotatingFileWriterEntryNew(file->basepath, + mode))) + goto error; + + return file; + + error: + virRotatingFileWriterFree(file); + return NULL; +} + + +/** + * virRotatingFileReaderNew: + * @path: the base path for files + * @maxbackup: number of backup files to read history from + * + * Create a new object for reading from a set of rolling files. + * I/O will start from the oldest file and proceed through + * files until the end of the newest one. + * + * If @maxbackup is zero the only the newest file will be read. + */ +virRotatingFileReaderPtr virRotatingFileReaderNew(const char *path, + size_t maxbackup) +{ + virRotatingFileReaderPtr file; + size_t i; + + if (VIR_ALLOC(file) < 0) + goto error; + + file->nentries = maxbackup + 1; + if (VIR_ALLOC_N(file->entries, file->nentries) < 0) + goto error; + + if (!(file->entries[file->nentries - 1] = virRotatingFileReaderEntryNew(path))) + goto error; + + for (i = 0; i < maxbackup; i++) { + char *tmppath; + if (virAsprintf(&tmppath, "%s.%zu", path, i) < 0) + goto error; + + file->entries[file->nentries - (i + 2)] = virRotatingFileReaderEntryNew(tmppath); + VIR_FREE(tmppath); + if (!file->entries[file->nentries - (i + 2)]) + goto error; + } + + return file; + + error: + virRotatingFileReaderFree(file); + return NULL; +} + + +/** + * virRotatingFileWriterGetPath: + * @file: the file context + * + * Return the primary file path + */ +const char *virRotatingFileWriterGetPath(virRotatingFileWriterPtr file) +{ + return file->basepath; +} + + +/** + * virRotatingFileWriterGetINode: + * @file: the file context + * + * Return the inode of the file currently being written to + */ +ino_t virRotatingFileWriterGetINode(virRotatingFileWriterPtr file) +{ + return file->entry->inode; +} + + +/** + * virRotatingFileWriterGetOffset: + * @file: the file context + * + * Return the offset at which data is currently being written + */ +off_t virRotatingFileWriterGetOffset(virRotatingFileWriterPtr file) +{ + return file->entry->pos; +} + + +static int virRotatingFileWriterRollover(virRotatingFileWriterPtr file) +{ + size_t i; + char *nextpath = NULL; + char *thispath = NULL; + int ret = -1; + + VIR_DEBUG("Rollover %s", file->basepath); + if (file->maxbackup == 0) { + if (unlink(file->basepath) < 0 && + errno != ENOENT) { + virReportSystemError(errno, + _("Unable to remove %s"), + file->basepath); + goto cleanup; + } + } else { + if (virAsprintf(&nextpath, "%s.%zu", file->basepath, file->maxbackup - 1) < 0) + return -1; + + for (i = file->maxbackup; i > 0; i--) { + if (i == 1) { + if (VIR_STRDUP(thispath, file->basepath) < 0) + goto cleanup; + } else { + if (virAsprintf(&thispath, "%s.%zu", file->basepath, 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->basepath); + + ret = 0; + cleanup: + VIR_FREE(nextpath); + VIR_FREE(thispath); + return ret; +} + + +/** + * virRotatingFileWriterAppend: + * @file: the file context + * @buf: the data buffer + * @len: the number of bytes in @buf + * + * Append the data in @buf to the file, performing rollover + * of the files if their size would exceed the limit + * + * Returns the number of bytes written, or -1 on error + */ +ssize_t virRotatingFileWriterAppend(virRotatingFileWriterPtr file, + const char *buf, + size_t len) +{ + ssize_t ret = 0; + size_t i; + while (len) { + size_t towrite = len; + bool forceRollover = false; + + if ((file->entry->pos + towrite) > file->maxlen) { + towrite = file->maxlen - file->entry->pos; + + /* + * If there's a newline in the last 80 chars + * we're about to write, then break at that + * point to avoid splitting lines across + * separate files + */ + for (i = 0; i < towrite && i < 80; i++) { + if (buf[towrite - i - 1] == '\n') { + towrite -= i; + forceRollover = true; + break; + } + } + } + + if (towrite) { + if (safewrite(file->entry->fd, buf, towrite) != towrite) { + virReportSystemError(errno, + _("Unable to write to file %s"), + file->basepath); + return -1; + } + + len -= towrite; + buf += towrite; + ret += towrite; + file->entry->pos += towrite; + file->entry->len += towrite; + } + + if ((file->entry->pos == file->maxlen && len) || + forceRollover) { + virRotatingFileWriterEntryPtr tmp = file->entry; + VIR_DEBUG("Hit max size %zu on %s (force=%d)\n", + file->maxlen, file->basepath, forceRollover); + + if (virRotatingFileWriterRollover(file) < 0) + return -1; + + if (!(file->entry = virRotatingFileWriterEntryNew(file->basepath, + file->mode))) + return -1; + + virRotatingFileWriterEntryFree(tmp); + } + } + + return ret; +} + + +/** + * virRotatingFileReaderSeek + * @file: the file context + * @inode: the inode of the file to seek to + * @offset: the offset within the file to seek to + * + * Seek to @offset in the file identified by @inode. + * If no file with a inode matching @inode currently + * exists, then seeks to the start of the oldest + * file, on the basis that the requested file has + * probably been rotated out of existance + */ +int virRotatingFileReaderSeek(virRotatingFileReaderPtr file, + ino_t inode, + off_t offset) +{ + size_t i; + off_t ret; + + for (i = 0; i < file->nentries; i++) { + virRotatingFileReaderEntryPtr entry = file->entries[i]; + if (entry->inode != inode || + entry->fd == -1) + continue; + + ret = lseek(entry->fd, offset, SEEK_SET); + if (ret == (off_t)-1) { + virReportSystemError(errno, + _("Unable to seek to inode %llu offset %llu"), + (unsigned long long)inode, (unsigned long long)offset); + return -1; + } + + file->current = i; + return 0; + } + + file->current = 0; + ret = lseek(file->entries[0]->fd, offset, SEEK_SET); + if (ret == (off_t)-1) { + virReportSystemError(errno, + _("Unable to seek to inode %llu offset %llu"), + (unsigned long long)inode, (unsigned long long)offset); + return -1; + } + return 0; +} + + +/** + * virRotatingFileReaderConsume: + * @file: the file context + * @buf: the buffer to fill with data + * @len: the size of @buf + * + * Reads data from the file starting at the current offset. + * The returned data may be pulled from multiple files. + * + * Returns: the number of bytes read or -1 on error + */ +ssize_t virRotatingFileReaderConsume(virRotatingFileReaderPtr file, + char *buf, + size_t len) +{ + ssize_t ret = 0; + + VIR_DEBUG("Consume %p %zu\n", buf, len); + while (len) { + virRotatingFileReaderEntryPtr entry; + ssize_t got; + + if (file->current >= file->nentries) + break; + + entry = file->entries[file->current]; + if (entry->fd == -1) { + file->current++; + continue; + } + + got = saferead(entry->fd, buf + ret, len); + if (got < 0) { + virReportSystemError(errno, + _("Unable to read from file %s"), + entry->path); + return -1; + } + + if (got == 0) { + file->current++; + continue; + } + + ret += got; + len -= got; + } + + return ret; +} + + +/** + * virRotatingFileWriterFree: + * @file: the file context + * + * Close the current file and release all resources + */ +void virRotatingFileWriterFree(virRotatingFileWriterPtr file) +{ + if (!file) + return; + + virRotatingFileWriterEntryFree(file->entry); + VIR_FREE(file->basepath); + VIR_FREE(file); +} + + +/** + * virRotatingFileReaderFree: + * @file: the file context + * + * Close the files and release all resources + */ +void virRotatingFileReaderFree(virRotatingFileReaderPtr file) +{ + size_t i; + + if (!file) + return; + + for (i = 0; i < file->nentries; i++) + virRotatingFileReaderEntryFree(file->entries[i]); + VIR_FREE(file->entries); + VIR_FREE(file); +} diff --git a/src/util/virrotatingfile.h b/src/util/virrotatingfile.h new file mode 100644 index 0000000..30cc8a5 --- /dev/null +++ b/src/util/virrotatingfile.h @@ -0,0 +1,62 @@ +/* + * virrotatingfile.h: reading/writing of 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 virRotatingFileWriter virRotatingFileWriter; +typedef virRotatingFileWriter *virRotatingFileWriterPtr; + +typedef struct virRotatingFileReader virRotatingFileReader; +typedef virRotatingFileReader *virRotatingFileReaderPtr; + +virRotatingFileWriterPtr virRotatingFileWriterNew(const char *path, + off_t maxlen, + size_t maxbackup, + bool truncate, + mode_t mode); + +virRotatingFileReaderPtr virRotatingFileReaderNew(const char *path, + size_t maxbackup); + +const char *virRotatingFileWriterGetPath(virRotatingFileWriterPtr file); + +ino_t virRotatingFileWriterGetINode(virRotatingFileWriterPtr file); +off_t virRotatingFileWriterGetOffset(virRotatingFileWriterPtr file); + +ssize_t virRotatingFileWriterAppend(virRotatingFileWriterPtr file, + const char *buf, + size_t len); + +int virRotatingFileReaderSeek(virRotatingFileReaderPtr file, + ino_t inode, + off_t offset); + +ssize_t virRotatingFileReaderConsume(virRotatingFileReaderPtr file, + char *buf, + size_t len); + +void virRotatingFileWriterFree(virRotatingFileWriterPtr file); +void virRotatingFileReaderFree(virRotatingFileReaderPtr 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..ed55e63 --- /dev/null +++ b/tests/virrotatingfiletest.c @@ -0,0 +1,698 @@ +/* + * 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" + +#define FILEBYTE 0xde +#define FILEBYTE0 0xad +#define FILEBYTE1 0xbe + +static int testRotatingFileWriterAssertOneFileSize(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 not %zu\n", + filename, size, sb.st_size); + return -1; + } else { + return 0; + } + } +} + +static int testRotatingFileWriterAssertFileSizes(off_t baseSize, + off_t backup0Size, + off_t backup1Size) +{ + if (testRotatingFileWriterAssertOneFileSize(FILENAME, baseSize) < 0 || + testRotatingFileWriterAssertOneFileSize(FILENAME0, backup0Size) < 0 || + testRotatingFileWriterAssertOneFileSize(FILENAME1, backup1Size) < 0) + return -1; + return 0; +} + + +static int testRotatingFileReaderAssertBufferContent(const char *buf, + size_t buflen, + size_t nregions, + size_t *sizes) +{ + size_t i, j; + char bytes[] = { FILEBYTE, FILEBYTE0, FILEBYTE1 }; + size_t total = 0; + + if (nregions > ARRAY_CARDINALITY(bytes)) { + fprintf(stderr, "Too many regions %zu\n", nregions); + return -1; + } + + for (i = 0; i < nregions; i++) + total += sizes[i]; + + if (total != buflen) { + fprintf(stderr, "Expected %zu bytes in file not %zu\n", + total, buflen); + return -1; + } + + for (i = 0; i < nregions; i++) { + char want = bytes[nregions - (i + 1)]; + for (j = 0; j < sizes[i]; j++) { + if (*buf != want) { + fprintf(stderr, + "Expected '0x%x' but got '0x%x' at region %zu byte %zu\n", + want & 0xff, *buf & 0xff, i, j); + return -1; + } + buf++; + } + } + + return 0; +} + + +static int testRotatingFileInitOne(const char *filename, + off_t size, + char pattern) +{ + 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, pattern, 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, FILEBYTE) < 0 || + testRotatingFileInitOne(FILENAME0, backup0Size, FILEBYTE0) < 0 || + testRotatingFileInitOne(FILENAME1, backup1Size, FILEBYTE1) < 0) { + return -1; + } + return 0; +} + +static int testRotatingFileWriterNew(const void *data ATTRIBUTE_UNUSED) +{ + virRotatingFileWriterPtr file; + int ret = -1; + char buf[512]; + + if (testRotatingFileInitFiles((off_t)-1, + (off_t)-1, + (off_t)-1) < 0) + return -1; + + file = virRotatingFileWriterNew(FILENAME, + 1024, + 2, + false, + 0700); + if (!file) + goto cleanup; + + if (testRotatingFileWriterAssertFileSizes(0, + (off_t)-1, + (off_t)-1) < 0) + goto cleanup; + + memset(buf, 0x5e, sizeof(buf)); + + virRotatingFileWriterAppend(file, buf, sizeof(buf)); + + if (testRotatingFileWriterAssertFileSizes(sizeof(buf), + (off_t)-1, + (off_t)-1) < 0) + goto cleanup; + + ret = 0; + cleanup: + virRotatingFileWriterFree(file); + unlink(FILENAME); + unlink(FILENAME0); + unlink(FILENAME1); + return ret; +} + + +static int testRotatingFileWriterAppend(const void *data ATTRIBUTE_UNUSED) +{ + virRotatingFileWriterPtr file; + int ret = -1; + char buf[512]; + + if (testRotatingFileInitFiles(512, + (off_t)-1, + (off_t)-1) < 0) + return -1; + + file = virRotatingFileWriterNew(FILENAME, + 1024, + 2, + false, + 0700); + if (!file) + goto cleanup; + + if (testRotatingFileWriterAssertFileSizes(512, + (off_t)-1, + (off_t)-1) < 0) + goto cleanup; + + memset(buf, 0x5e, sizeof(buf)); + + virRotatingFileWriterAppend(file, buf, sizeof(buf)); + + if (testRotatingFileWriterAssertFileSizes(1024, + (off_t)-1, + (off_t)-1) < 0) + goto cleanup; + + ret = 0; + cleanup: + virRotatingFileWriterFree(file); + unlink(FILENAME); + unlink(FILENAME0); + unlink(FILENAME1); + return ret; +} + + +static int testRotatingFileWriterTruncate(const void *data ATTRIBUTE_UNUSED) +{ + virRotatingFileWriterPtr file; + int ret = -1; + char buf[512]; + + if (testRotatingFileInitFiles(512, + (off_t)-1, + (off_t)-1) < 0) + return -1; + + file = virRotatingFileWriterNew(FILENAME, + 1024, + 2, + true, + 0700); + if (!file) + goto cleanup; + + if (testRotatingFileWriterAssertFileSizes(0, + (off_t)-1, + (off_t)-1) < 0) + goto cleanup; + + memset(buf, 0x5e, sizeof(buf)); + + virRotatingFileWriterAppend(file, buf, sizeof(buf)); + + if (testRotatingFileWriterAssertFileSizes(512, + (off_t)-1, + (off_t)-1) < 0) + goto cleanup; + + ret = 0; + cleanup: + virRotatingFileWriterFree(file); + unlink(FILENAME); + unlink(FILENAME0); + unlink(FILENAME1); + return ret; +} + + +static int testRotatingFileWriterRolloverNone(const void *data ATTRIBUTE_UNUSED) +{ + virRotatingFileWriterPtr file; + int ret = -1; + char buf[512]; + + if (testRotatingFileInitFiles((off_t)-1, + (off_t)-1, + (off_t)-1) < 0) + return -1; + + file = virRotatingFileWriterNew(FILENAME, + 200, + 0, + false, + 0700); + if (!file) + goto cleanup; + + if (testRotatingFileWriterAssertFileSizes(0, + (off_t)-1, + (off_t)-1) < 0) + goto cleanup; + + memset(buf, 0x5e, sizeof(buf)); + + virRotatingFileWriterAppend(file, buf, sizeof(buf)); + + if (testRotatingFileWriterAssertFileSizes(112, + (off_t)-1, + (off_t)-1) < 0) + goto cleanup; + + ret = 0; + cleanup: + virRotatingFileWriterFree(file); + unlink(FILENAME); + unlink(FILENAME0); + unlink(FILENAME1); + return ret; +} + + +static int testRotatingFileWriterRolloverOne(const void *data ATTRIBUTE_UNUSED) +{ + virRotatingFileWriterPtr file; + int ret = -1; + char buf[512]; + + if (testRotatingFileInitFiles((off_t)-1, + (off_t)-1, + (off_t)-1) < 0) + return -1; + + file = virRotatingFileWriterNew(FILENAME, + 1024, + 2, + false, + 0700); + if (!file) + goto cleanup; + + if (testRotatingFileWriterAssertFileSizes(0, + (off_t)-1, + (off_t)-1) < 0) + goto cleanup; + + memset(buf, 0x5e, sizeof(buf)); + + virRotatingFileWriterAppend(file, buf, sizeof(buf)); + virRotatingFileWriterAppend(file, buf, sizeof(buf)); + virRotatingFileWriterAppend(file, buf, sizeof(buf)); + + if (testRotatingFileWriterAssertFileSizes(512, + 1024, + (off_t)-1) < 0) + goto cleanup; + + ret = 0; + cleanup: + virRotatingFileWriterFree(file); + unlink(FILENAME); + unlink(FILENAME0); + unlink(FILENAME1); + return ret; +} + + +static int testRotatingFileWriterRolloverAppend(const void *data ATTRIBUTE_UNUSED) +{ + virRotatingFileWriterPtr file; + int ret = -1; + char buf[512]; + + if (testRotatingFileInitFiles((off_t)768, + (off_t)-1, + (off_t)-1) < 0) + return -1; + + file = virRotatingFileWriterNew(FILENAME, + 1024, + 2, + false, + 0700); + if (!file) + goto cleanup; + + if (testRotatingFileWriterAssertFileSizes(768, + (off_t)-1, + (off_t)-1) < 0) + goto cleanup; + + memset(buf, 0x5e, sizeof(buf)); + + virRotatingFileWriterAppend(file, buf, sizeof(buf)); + + if (testRotatingFileWriterAssertFileSizes(256, + 1024, + (off_t)-1) < 0) + goto cleanup; + + ret = 0; + cleanup: + virRotatingFileWriterFree(file); + unlink(FILENAME); + unlink(FILENAME0); + unlink(FILENAME1); + return ret; +} + + +static int testRotatingFileWriterRolloverMany(const void *data ATTRIBUTE_UNUSED) +{ + virRotatingFileWriterPtr file; + int ret = -1; + char buf[512]; + + if (testRotatingFileInitFiles((off_t)-1, + (off_t)-1, + (off_t)-1) < 0) + return -1; + + file = virRotatingFileWriterNew(FILENAME, + 1024, + 2, + false, + 0700); + if (!file) + goto cleanup; + + if (testRotatingFileWriterAssertFileSizes(0, + (off_t)-1, + (off_t)-1) < 0) + goto cleanup; + + memset(buf, 0x5e, sizeof(buf)); + + virRotatingFileWriterAppend(file, buf, sizeof(buf)); + virRotatingFileWriterAppend(file, buf, sizeof(buf)); + virRotatingFileWriterAppend(file, buf, sizeof(buf)); + virRotatingFileWriterAppend(file, buf, sizeof(buf)); + virRotatingFileWriterAppend(file, buf, sizeof(buf)); + virRotatingFileWriterAppend(file, buf, sizeof(buf)); + virRotatingFileWriterAppend(file, buf, sizeof(buf)); + + if (testRotatingFileWriterAssertFileSizes(512, + 1024, + 1024) < 0) + goto cleanup; + + ret = 0; + cleanup: + virRotatingFileWriterFree(file); + unlink(FILENAME); + unlink(FILENAME0); + unlink(FILENAME1); + return ret; +} + + +static int testRotatingFileWriterRolloverLineBreak(const void *data ATTRIBUTE_UNUSED) +{ + virRotatingFileWriterPtr file; + int ret = -1; + const char *buf = "The quick brown fox jumps over the lazy dog\n" + "The wizard quickly jinxed the gnomes before they vaporized\n"; + + if (testRotatingFileInitFiles(100, + (off_t)-1, + (off_t)-1) < 0) + return -1; + + file = virRotatingFileWriterNew(FILENAME, + 160, + 2, + false, + 0700); + if (!file) + goto cleanup; + + if (testRotatingFileWriterAssertFileSizes(100, + (off_t)-1, + (off_t)-1) < 0) + goto cleanup; + + virRotatingFileWriterAppend(file, buf, strlen(buf)); + + if (testRotatingFileWriterAssertFileSizes(59, + 144, + (off_t)-1) < 0) + goto cleanup; + + ret = 0; + cleanup: + virRotatingFileWriterFree(file); + unlink(FILENAME); + unlink(FILENAME0); + unlink(FILENAME1); + return ret; +} + + +static int testRotatingFileReaderOne(const void *data ATTRIBUTE_UNUSED) +{ + virRotatingFileReaderPtr file; + int ret = -1; + char buf[512]; + ssize_t got; + size_t regions[] = { 256 }; + + if (testRotatingFileInitFiles(256, (off_t)-1, (off_t)-1) < 0) + return -1; + + file = virRotatingFileReaderNew(FILENAME, 2); + if (!file) + goto cleanup; + + if ((got = virRotatingFileReaderConsume(file, buf, sizeof(buf))) < 0) + goto cleanup; + + if (testRotatingFileReaderAssertBufferContent(buf, got, + ARRAY_CARDINALITY(regions), + regions) < 0) + goto cleanup; + + ret = 0; + cleanup: + virRotatingFileReaderFree(file); + unlink(FILENAME); + unlink(FILENAME0); + unlink(FILENAME1); + return ret; +} + +static int testRotatingFileReaderAll(const void *data ATTRIBUTE_UNUSED) +{ + virRotatingFileReaderPtr file; + int ret = -1; + char buf[768]; + ssize_t got; + size_t regions[] = { 256, 256, 256 }; + + if (testRotatingFileInitFiles(256, 256, 256) < 0) + return -1; + + file = virRotatingFileReaderNew(FILENAME, 2); + if (!file) + goto cleanup; + + if ((got = virRotatingFileReaderConsume(file, buf, sizeof(buf))) < 0) + goto cleanup; + + if (testRotatingFileReaderAssertBufferContent(buf, got, + ARRAY_CARDINALITY(regions), + regions) < 0) + goto cleanup; + + ret = 0; + cleanup: + virRotatingFileReaderFree(file); + unlink(FILENAME); + unlink(FILENAME0); + unlink(FILENAME1); + return ret; +} + +static int testRotatingFileReaderPartial(const void *data ATTRIBUTE_UNUSED) +{ + virRotatingFileReaderPtr file; + int ret = -1; + char buf[600]; + ssize_t got; + size_t regions[] = { 256, 256, 88 }; + + if (testRotatingFileInitFiles(256, 256, 256) < 0) + return -1; + + file = virRotatingFileReaderNew(FILENAME, 2); + if (!file) + goto cleanup; + + if ((got = virRotatingFileReaderConsume(file, buf, sizeof(buf))) < 0) + goto cleanup; + + if (testRotatingFileReaderAssertBufferContent(buf, got, + ARRAY_CARDINALITY(regions), + regions) < 0) + goto cleanup; + + ret = 0; + cleanup: + virRotatingFileReaderFree(file); + unlink(FILENAME); + unlink(FILENAME0); + unlink(FILENAME1); + return ret; +} + +static int testRotatingFileReaderSeek(const void *data ATTRIBUTE_UNUSED) +{ + virRotatingFileReaderPtr file; + int ret = -1; + char buf[600]; + ssize_t got; + size_t regions[] = { 156, 256 }; + struct stat sb; + + if (testRotatingFileInitFiles(256, 256, 256) < 0) + return -1; + + file = virRotatingFileReaderNew(FILENAME, 2); + if (!file) + goto cleanup; + + if (stat(FILENAME0, &sb) < 0) { + virReportSystemError(errno, "Cannot stat %s", FILENAME0); + goto cleanup; + } + + if (virRotatingFileReaderSeek(file, sb.st_ino, 100) < 0) + goto cleanup; + + if ((got = virRotatingFileReaderConsume(file, buf, sizeof(buf))) < 0) + goto cleanup; + + if (testRotatingFileReaderAssertBufferContent(buf, got, + ARRAY_CARDINALITY(regions), + regions) < 0) + goto cleanup; + + ret = 0; + cleanup: + virRotatingFileReaderFree(file); + unlink(FILENAME); + unlink(FILENAME0); + unlink(FILENAME1); + return ret; +} + +static int +mymain(void) +{ + int ret = 0; + + if (virtTestRun("Rotating file write new", testRotatingFileWriterNew, NULL) < 0) + ret = -1; + + if (virtTestRun("Rotating file write append", testRotatingFileWriterAppend, NULL) < 0) + ret = -1; + + if (virtTestRun("Rotating file write truncate", testRotatingFileWriterTruncate, NULL) < 0) + ret = -1; + + if (virtTestRun("Rotating file write rollover no backup", testRotatingFileWriterRolloverNone, NULL) < 0) + ret = -1; + + if (virtTestRun("Rotating file write rollover one", testRotatingFileWriterRolloverOne, NULL) < 0) + ret = -1; + + if (virtTestRun("Rotating file write rollover append", testRotatingFileWriterRolloverAppend, NULL) < 0) + ret = -1; + + if (virtTestRun("Rotating file write rollover many", testRotatingFileWriterRolloverMany, NULL) < 0) + ret = -1; + + if (virtTestRun("Rotating file write rollover line break", testRotatingFileWriterRolloverLineBreak, NULL) < 0) + ret = -1; + + if (virtTestRun("Rotating file read one", testRotatingFileReaderOne, NULL) < 0) + ret = -1; + + if (virtTestRun("Rotating file read all", testRotatingFileReaderAll, NULL) < 0) + ret = -1; + + if (virtTestRun("Rotating file read partial", testRotatingFileReaderPartial, NULL) < 0) + ret = -1; + + if (virtTestRun("Rotating file read seek", testRotatingFileReaderSeek, NULL) < 0) + ret = -1; + + return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +VIRT_TEST_MAIN(mymain) -- 2.5.0

On Thu, Nov 12, 2015 at 17:18:58 +0000, Daniel Berrange wrote:
Add virRotatingFileReader and virRotatingFileWriter objects which allow reading & writing from/to files 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> ---
[...]
diff --git a/src/util/virrotatingfile.c b/src/util/virrotatingfile.c new file mode 100644 index 0000000..840b55f --- /dev/null +++ b/src/util/virrotatingfile.c @@ -0,0 +1,608 @@
[...]
+ + +/** + * virRotatingFileWriterGetPath: + * @file: the file context + * + * Return the primary file path + */ +const char *virRotatingFileWriterGetPath(virRotatingFileWriterPtr file) +{ + return file->basepath; +} + + +/** + * virRotatingFileWriterGetINode: + * @file: the file context + * + * Return the inode of the file currently being written to + */ +ino_t virRotatingFileWriterGetINode(virRotatingFileWriterPtr file) +{ + return file->entry->inode; +} + + +/** + * virRotatingFileWriterGetOffset: + * @file: the file context + * + * Return the offset at which data is currently being written + */ +off_t virRotatingFileWriterGetOffset(virRotatingFileWriterPtr file) +{ + return file->entry->pos; +}
I see how you are going to use this. I think the usage pattern is a bit complicated, but for the purpose it will serve it's probably all right. ACK Peter

On 11/12/2015 12:18 PM, Daniel P. Berrange wrote:
Add virRotatingFileReader and virRotatingFileWriter objects which allow reading & writing from/to files 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 | 13 + src/util/virrotatingfile.c | 608 ++++++++++++++++++++++++++++++++++++++ src/util/virrotatingfile.h | 62 ++++ tests/Makefile.am | 6 + tests/virrotatingfiletest.c | 698 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1389 insertions(+) create mode 100644 src/util/virrotatingfile.c create mode 100644 src/util/virrotatingfile.h create mode 100644 tests/virrotatingfiletest.c
[...]
--- /dev/null +++ b/src/util/virrotatingfile.c @@ -0,0 +1,608 @@
In the grand scheme of things a nit - lately there seems to be an effort to use two lines for functions, such as: [static] int virFunctionName(param1Type param1, param2Type param2) This module has some that are and some that aren't. I don't have heart break one way or another, but someone may ;-)
+/* + * 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/>. + * + */ +
[...]
+ + +static virRotatingFileReaderEntryPtr +virRotatingFileReaderEntryNew(const char *path) +{ + virRotatingFileReaderEntryPtr entry; + struct stat sb; + + VIR_DEBUG("Opening %s", path); + + if (VIR_ALLOC(entry) < 0) + return NULL; + + if (VIR_STRDUP(entry->path, path) < 0) + goto error;
Should the open occur first; otherwise, entry->fd == 0 and the *Free routine will VIR_FORCE_CLOSE(0);
+ + if ((entry->fd = open(path, O_RDONLY)) < 0) { + if (errno != ENOENT) { + virReportSystemError(errno, + _("Unable to open file: %s"), path); + goto error; + } + } + + if (entry->fd != -1) { + if (fstat(entry->fd, &sb) < 0) { + virReportSystemError(errno, + _("Unable to determine current file inode: %s"), + path); + goto error; + } + + entry->inode = sb.st_ino; + } + + return entry; + + error: + virRotatingFileReaderEntryFree(entry); + return NULL; +} +
[...]
+/** + * virRotatingFileWriterNew + * @path: the base path for files + * @maxlen: the maximum number of bytes to write before rollover + * @maxbackup: number of backup files to keep when rolloing over
rolling over Although I don't see it being documented/used later in/for some config file (and the max value I see used in code is 2)... Should there be some sort of "sanity check" that maxbackup doesn't exceed some number - only so many files can be open per process, right? Then of course there's the silly test of someone passing -1... Also, as a config value it seems if someone changes the maxbackup value (higher to lower), then some algorithms may miss files... If then going from lower to higher, then files that may not have been deleted might be found. ACK - John
+ * @truncate: whether to truncate the current files when opening + * @mode: the file mode to use for creating new files + * + * Create a new object for writing data to a file with + * automatic rollover. If @maxbackup is zero, no backup + * files will be created. The primary file will just get + * truncated and reopened. + * + * The files will never exceed @maxlen bytes in size, + * but may be rolled over before they reach this size + * in order to avoid splitting lines + */ +virRotatingFileWriterPtr virRotatingFileWriterNew(const char *path, + off_t maxlen, + size_t maxbackup, + bool truncate, + mode_t mode) +{ + virRotatingFileWriterPtr file; + + if (VIR_ALLOC(file) < 0) + goto error; + + if (VIR_STRDUP(file->basepath, path) < 0) + goto error; + + file->mode = mode; + file->maxbackup = maxbackup; + file->maxlen = maxlen; + + if (truncate && + virRotatingFileWriterDelete(file) < 0) + goto error; + + if (!(file->entry = virRotatingFileWriterEntryNew(file->basepath, + mode))) + goto error; + + return file; + + error: + virRotatingFileWriterFree(file); + return NULL; +} + +

On Wed, Nov 18, 2015 at 08:46:43AM -0500, John Ferlan wrote:
On 11/12/2015 12:18 PM, Daniel P. Berrange wrote:
Add virRotatingFileReader and virRotatingFileWriter objects which allow reading & writing from/to files 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 | 13 + src/util/virrotatingfile.c | 608 ++++++++++++++++++++++++++++++++++++++ src/util/virrotatingfile.h | 62 ++++ tests/Makefile.am | 6 + tests/virrotatingfiletest.c | 698 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1389 insertions(+) create mode 100644 src/util/virrotatingfile.c create mode 100644 src/util/virrotatingfile.h create mode 100644 tests/virrotatingfiletest.c
[...]
--- /dev/null +++ b/src/util/virrotatingfile.c @@ -0,0 +1,608 @@
In the grand scheme of things a nit - lately there seems to be an effort to use two lines for functions, such as:
[static] int virFunctionName(param1Type param1, param2Type param2)
This module has some that are and some that aren't. I don't have heart break one way or another, but someone may ;-)
+/* + * 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/>. + * + */ +
[...]
+ + +static virRotatingFileReaderEntryPtr +virRotatingFileReaderEntryNew(const char *path) +{ + virRotatingFileReaderEntryPtr entry; + struct stat sb; + + VIR_DEBUG("Opening %s", path); + + if (VIR_ALLOC(entry) < 0) + return NULL; + + if (VIR_STRDUP(entry->path, path) < 0) + goto error;
Should the open occur first; otherwise, entry->fd == 0 and the *Free routine will VIR_FORCE_CLOSE(0);
Yep, correct.
Although I don't see it being documented/used later in/for some config file (and the max value I see used in code is 2)... Should there be some sort of "sanity check" that maxbackup doesn't exceed some number - only so many files can be open per process, right? Then of course there's the silly test of someone passing -1...
Well it is defined a size_t so you "cant" pass -1, but yeah we should refuse greater than say 20 backup files.
Also, as a config value it seems if someone changes the maxbackup value (higher to lower), then some algorithms may miss files... If then going from lower to higher, then files that may not have been deleted might be found.
IMHO if changing from higher to lower it is the admins responsibility to kill off obsolete files. 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. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- .gitignore | 7 + cfg.mk | 4 +- include/libvirt/virterror.h | 1 + libvirt.spec.in | 24 +- po/POTFILES.in | 2 + src/Makefile.am | 169 +++++- 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, 2156 insertions(+), 22 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 db513be..2a23b33 100644 --- a/cfg.mk +++ b/cfg.mk @@ -1112,7 +1112,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$$ @@ -1147,7 +1147,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 ac46da5..8420d11 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..3323bd1 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 \ @@ -1838,7 +1873,8 @@ check-local: check-augeas $(NULL) check-augeas: check-augeas-qemu check-augeas-lxc check-augeas-sanlock \ - check-augeas-lockd check-augeas-virtlockd check-augeas-libxl + 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

On Thu, Nov 12, 2015 at 17:18:59 +0000, Daniel Berrange wrote:
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.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> ---
[...]
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.
Um 2006? Here and in every other header. [...]
+ +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",
Will this need to call libvirt? Or should this be 'virtlogd'?
+ "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 +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.
The logging ring buffer isn't present any more.
+ */ +static int +virLogDaemonSetupLogging(virLogDaemonConfigPtr config, + bool privileged, + bool verbose, + bool godaemon) +{ + virLogReset(); + + /* + * Libvirtd's order of precedence is:
libvirtd? [...]
+ +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);
Log management. [...]
+#define MAX_LISTEN 5
This macro isn't used in this patch.
+int main(int argc, char **argv) {
[...]
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 @@
[...]
+/* Read the config file if it exists. + * Only used in the remote case, hence the name.
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; +}
[...]
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;
This is going to be modified in the next patch. Shouldn't you use the right value directly here? [...]
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\"
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" }
^^ see below.
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
journald is used as default when initializing, so it should be docummented here.
+# 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 = 64a
If it's not used, it should not be part of the new daemon any more.
+ +# 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
Should we mention this also in the libvirtd config file?
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 COPYRIGHT + +Copyright (C) 2006-2015 Red Hat, Inc., and the authors listed in the +libvirt AUTHORS file.
2006? ACK, with suggesed cleanups. I wasn't entirely thorough when reviewing this since it is 75k of new stuff. Peter

On Wed, Nov 18, 2015 at 03:54:37PM +0100, Peter Krempa wrote:
On Thu, Nov 12, 2015 at 17:18:59 +0000, Daniel Berrange wrote:
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.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> ---
[...]
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.
Um 2006? Here and in every other header.
All this code is cut+paste from the virtlockd code, which is in turn cut+paste from libvirtd. So preserving the original copyright lines as-is is appropriate.
+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",
Will this need to call libvirt? Or should this be 'virtlogd'?
I think I can probably delete this error entirely - I think it is unused.
+/* + * 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.
The logging ring buffer isn't present any more.
Hah, yeah, should be killed.
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;
This is going to be modified in the next patch. Shouldn't you use the right value directly here?
Yep, good point.
+ +# 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
Should we mention this also in the libvirtd config file?
No, this doesn't have an impact on the max_clients requirement for libvirtd. 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 11/12/2015 12:18 PM, Daniel P. Berrange wrote:
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.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- .gitignore | 7 + cfg.mk | 4 +- include/libvirt/virterror.h | 1 + libvirt.spec.in | 24 +- po/POTFILES.in | 2 + src/Makefile.am | 169 +++++- 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, 2156 insertions(+), 22 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
Full disclosure - the aspects of Makefiles, cfg files, spec files, etc. - not my area of expertise... Looks like things were copied correctly though and it does build... Whether it builds on all platforms for all strange variants of make - I'll leave to existing build processes... [...] Hopefully some assumptions can be made regarding how much of this is copied from lockd ;-)
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> + */
[...]
+static void +virLogDaemonFree(virLogDaemonPtr logd) +{ + if (!logd) + return;
Should there be a virMutexDestroy(logd->lock); ? If so, it's also missing from lockd
+ + virObjectUnref(logd->srv); + virObjectUnref(logd->dmn); + + VIR_FREE(logd); +} + +
[...]
+ +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);
VIR_FREE(rundir); - I see this is true in lockd too...
+ 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);
VIR_FREE(logdir); - same in lockd
+ 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; +} + + +
[...]
+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:
Could use virLogDaemonClientFree()
+ virMutexDestroy(&priv->lock); + VIR_FREE(priv); + return NULL; +} + +
[...]
+ +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);
s/lock/log/
+ + 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); ^ Is 'l' valid? Same for lockd BTW
+ + 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) { ^ Extra space
+ 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) { ^ Extra space (repeats a couple times...))
+ 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) {
extra space
+ 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;
should we umask(old_umask) here?
+ 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); + + +
There's an extra line here. /* 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); +}
[...] Admittedly I know very little about the log_protocol.x and other src/logging/* files from here... I do note that test_virtlogd.aug.in doesn't have 'max_clients', although virtlogd.aug has a reference. I also note there's a log_buffer_size listed, but no corresponding element in the virLogDaemonConfig (or of course read of such element).
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 ]
't' timeout? 'V' version (yes, I see --version)
+ +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> +
't' 'timeout'...
+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
ACK - with a couple of minor adjustments. John

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 | 101 +++++++- src/logging/log_handler.c | 521 ++++++++++++++++++++++++++++++++++++++ src/logging/log_handler.h | 63 +++++ src/logging/log_protocol.x | 94 ++++++- 8 files changed, 815 insertions(+), 2 deletions(-) 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 3323bd1..c658204 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..f612be1 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,108 @@ #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, + virLogManagerProtocolDomainOpenLogFileRet *ret) +{ + int fd = -1; + int rv = -1; + off_t offset; + ino_t inode; + + if ((fd = virLogHandlerDomainOpenLogFile(virLogDaemonGetHandler(logDaemon), + args->driver, + (unsigned char *)args->dom.uuid, + args->dom.name, + &inode, &offset)) < 0) + goto cleanup; + + ret->pos.inode = inode; + ret->pos.offset = offset; + + if (virNetMessageAddFD(msg, fd) < 0) + goto cleanup; + + rv = 1; /* '1' tells caller we added some FDs */ + + cleanup: + VIR_FORCE_CLOSE(fd); + if (rv < 0) + virNetMessageSaveError(rerr); + return rv; +} + + +static int +virLogManagerProtocolDispatchDomainGetLogFilePosition(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client ATTRIBUTE_UNUSED, + virNetMessagePtr msg ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + virLogManagerProtocolDomainGetLogFilePositionArgs *args, + virLogManagerProtocolDomainGetLogFilePositionRet *ret) +{ + int rv = -1; + off_t offset; + ino_t inode; + + if (virLogHandlerDomainGetLogFilePosition(virLogDaemonGetHandler(logDaemon), + args->driver, + (unsigned char *)args->dom.uuid, + args->dom.name, + &inode, &offset) < 0) + goto cleanup; + + ret->pos.inode = inode; + ret->pos.offset = offset; + + rv = 0; + cleanup: + + if (rv < 0) + virNetMessageSaveError(rerr); + return rv; +} + + +static int +virLogManagerProtocolDispatchDomainReadLogFile(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client ATTRIBUTE_UNUSED, + virNetMessagePtr msg ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + virLogManagerProtocolDomainReadLogFileArgs *args, + virLogManagerProtocolDomainReadLogFileRet *ret) +{ + int rv = -1; + char *data; + + if ((data = virLogHandlerDomainReadLogFile(virLogDaemonGetHandler(logDaemon), + args->driver, + (unsigned char *)args->dom.uuid, + args->dom.name, + args->pos.inode, + args->pos.offset, + args->maxlen)) == NULL) + goto cleanup; + + ret->data = data; + + rv = 0; + + cleanup: + if (rv < 0) + virNetMessageSaveError(rerr); + return rv; +} diff --git a/src/logging/log_handler.c b/src/logging/log_handler.c new file mode 100644 index 0000000..8853fd0 --- /dev/null +++ b/src/logging/log_handler.c @@ -0,0 +1,521 @@ +/* + * 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 { + virRotatingFileWriterPtr 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); + virRotatingFileWriterFree(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 (virRotatingFileWriterAppend(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 = virRotatingFileWriterNew(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, + ino_t *inode, + off_t *offset) +{ + 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(virRotatingFileWriterGetPath(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 = virRotatingFileWriterNew(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); + + *inode = virRotatingFileWriterGetINode(file->file); + *offset = virRotatingFileWriterGetOffset(file->file); + + 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; +} + + +int virLogHandlerDomainGetLogFilePosition(virLogHandlerPtr handler, + const char *driver, + const unsigned char *domuuid, + const char *domname, + ino_t *inode, + off_t *offset) +{ + char *path; + virLogHandlerLogFilePtr file = NULL; + int ret = -1; + size_t i; + + virObjectLock(handler); + + if (!(path = virLogHandlerGetLogFilePathForDomain(handler, + driver, + domuuid, + domname))) + goto cleanup; + + for (i = 0; i < handler->nfiles; i++) { + if (STREQ(virRotatingFileWriterGetPath(handler->files[i]->file), + path)) { + file = handler->files[i]; + break; + } + } + + if (!file) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("No open log file for domain %s"), + domname); + goto cleanup; + } + + *inode = virRotatingFileWriterGetINode(file->file); + *offset = virRotatingFileWriterGetOffset(file->file); + + ret = 0; + + cleanup: + VIR_FREE(path); + virObjectUnlock(handler); + return ret; +} + + +char *virLogHandlerDomainReadLogFile(virLogHandlerPtr handler, + const char *driver, + const unsigned char *domuuid, + const char *domname, + ino_t inode, + off_t offset, + size_t maxlen) +{ + char *path; + virRotatingFileReaderPtr file = NULL; + char *data = NULL; + + if (!(path = virLogHandlerGetLogFilePathForDomain(handler, + driver, + domuuid, + domname))) + goto error; + + if (!(file = virRotatingFileReaderNew(path, 2))) + goto error; + + if (virRotatingFileReaderSeek(file, inode, offset) < 0) + goto error; + + if (VIR_ALLOC_N(data, maxlen) < 0) + goto error; + + if (virRotatingFileReaderConsume(file, data, maxlen) < 0) + goto error; + + virRotatingFileReaderFree(file); + return data; + + error: + VIR_FREE(data); + virRotatingFileReaderFree(file); + return NULL; +} + + +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", + virRotatingFileWriterGetPath(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..1ad755e --- /dev/null +++ b/src/logging/log_handler.h @@ -0,0 +1,63 @@ +/* + * 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, + ino_t *inode, + off_t *offset); + +int virLogHandlerDomainGetLogFilePosition(virLogHandlerPtr handler, + const char *driver, + const unsigned char *domuuid, + const char *domname, + ino_t *inode, + off_t *offset); + +char *virLogHandlerDomainReadLogFile(virLogHandlerPtr handler, + const char *driver, + const unsigned char *domuuid, + const char *domname, + ino_t inode, + off_t offset, + size_t maxlen); + +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..2702beb 100644 --- a/src/logging/log_protocol.x +++ b/src/logging/log_protocol.x @@ -9,7 +9,7 @@ typedef opaque virLogManagerProtocolUUID[VIR_UUID_BUFLEN]; * 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; +const VIR_LOG_MANAGER_PROTOCOL_STRING_MAX = 4194304; /* A long string, which may NOT be NULL. */ typedef string virLogManagerProtocolNonNullString<VIR_LOG_MANAGER_PROTOCOL_STRING_MAX>; @@ -17,6 +17,98 @@ 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; + +struct virLogManagerProtocolLogFilePosition { + unsigned hyper inode; + unsigned hyper offset; +}; +typedef struct virLogManagerProtocolLogFilePosition virLogManagerProtocolLogFilePosition; + +/* Obtain a file handle suitable for writing to a + * log file for a domain + */ +struct virLogManagerProtocolDomainOpenLogFileArgs { + virLogManagerProtocolNonNullString driver; + virLogManagerProtocolDomain dom; + unsigned int flags; +}; + +struct virLogManagerProtocolDomainOpenLogFileRet { + virLogManagerProtocolLogFilePosition pos; +}; + +struct virLogManagerProtocolDomainGetLogFilePositionArgs { + virLogManagerProtocolNonNullString driver; + virLogManagerProtocolDomain dom; + unsigned int flags; +}; + +struct virLogManagerProtocolDomainGetLogFilePositionRet { + virLogManagerProtocolLogFilePosition pos; +}; + +struct virLogManagerProtocolDomainReadLogFileArgs { + virLogManagerProtocolNonNullString driver; + virLogManagerProtocolDomain dom; + virLogManagerProtocolLogFilePosition pos; + unsigned hyper maxlen; +}; + +struct virLogManagerProtocolDomainReadLogFileRet { + virLogManagerProtocolNonNullString data; +}; + /* 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, + + /** + * @generate: none + * @acl: none + */ + VIR_LOG_MANAGER_PROTOCOL_PROC_DOMAIN_GET_LOG_FILE_POSITION = 2, + + /** + * @generate: none + * @acl: none + */ + VIR_LOG_MANAGER_PROTOCOL_PROC_DOMAIN_READ_LOG_FILE = 3 +}; -- 2.5.0

On 11/12/2015 12:19 PM, Daniel P. Berrange wrote:
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 | 101 +++++++- src/logging/log_handler.c | 521 ++++++++++++++++++++++++++++++++++++++ src/logging/log_handler.h | 63 +++++ src/logging/log_protocol.x | 94 ++++++- 8 files changed, 815 insertions(+), 2 deletions(-) create mode 100644 src/logging/log_handler.c create mode 100644 src/logging/log_handler.h
As I was thinking about the "view of a client" - I began to wonder if the logic in virRotatingFileReaderSeek which doesn't find the inode/offset should perhaps return a failure instead of the offset of the oldest file (especially if maxbackup == 0)? Is it possible to have a case where there are two backups and enough time has passed such that we've rotated more than once (or is that an Amsterdam moment ;-))? Shouldn't perhaps the client be able to handle whether the file it was looking at has rotated out of existence and then decide what to do rather than assuming that the client would want the offset of the most recent rotated file (if of course it even exists)? Another thought I had - within this new model might it be reasonable to allow a client to set the logging level desired for a connection rather than using the same logging level for all virtlogd connections? I know I've found use in the past of raising and lowering logging levels for debugging libvirtd, but I have to turn off all domains if I want more; otherwise, the log file is overwhelmed. But I could certainly see need if I were debugging to 'enable' or 'disable' specific logging "on the fly"... Different problem! Anyway... [...]
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
[...]
@@ -157,6 +162,12 @@ virLogDaemonNew(virLogDaemonConfigPtr config, bool privileged) }
+virLogHandlerPtr virLogDaemonGetHandler(virLogDaemonPtr daemon)
NIT: Two lines virLogHandlerPtr virLogDaemon...
+{ + return daemon->handler; +} + +
[...]
diff --git a/src/logging/log_daemon_dispatch.c b/src/logging/log_daemon_dispatch.c index 98df178..f612be1 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.
Probably should have been something in patch 2... I didn't mention it there mainly because I read your comment to Peter...
* * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public
[...]
diff --git a/src/logging/log_handler.c b/src/logging/log_handler.c new file mode 100644 index 0000000..8853fd0
[...]
+ +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);
handler is still locked
+ 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 (virRotatingFileWriterAppend(logfile->file, buf, len) != len) + goto error; + + if (events & VIR_EVENT_HANDLE_HANGUP) + goto error; + + virObjectUnlock(handler); + return; + + error: + virLogHandlerLogFileClose(handler, logfile); + virObjectUnlock(handler); +} + +
[...]
+ + +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 = virRotatingFileWriterNew(path, 128 * 1024, 2, false, 0600)) == NULL)
magic numbers (used more than once) #define DEFAULT_FILE_SIZE 128 * 1024 #define DEFAULT_MAXBACKUP 2 #define DEFAULT_MODE 0600
+ 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; +} +
[...]
+ +char *virLogHandlerDomainReadLogFile(virLogHandlerPtr handler, + const char *driver, + const unsigned char *domuuid, + const char *domname, + ino_t inode, + off_t offset, + size_t maxlen) +{ + char *path; + virRotatingFileReaderPtr file = NULL; + char *data = NULL;
Why no locking of handler?
+ + if (!(path = virLogHandlerGetLogFilePathForDomain(handler, + driver, + domuuid, + domname))) + goto error; + + if (!(file = virRotatingFileReaderNew(path, 2))) + goto error; + + if (virRotatingFileReaderSeek(file, inode, offset) < 0) + goto error; + + if (VIR_ALLOC_N(data, maxlen) < 0) + goto error; +
What happens if by the time we get here the file was rotated? Although the current default is 2 backups, if there were 0 backups, then what?
+ if (virRotatingFileReaderConsume(file, data, maxlen) < 0) + goto error; + + virRotatingFileReaderFree(file); + return data;
path is leaked (both regular and error)
+ + error: + VIR_FREE(data); + virRotatingFileReaderFree(file); + return NULL; +} + +
[...] That's what I found - I don't think there's anything that I found that would prevent an ACK, although perhaps depending on the answer to the question posed at the top regarding the view of the client more changes would be required in this code to handle the case(s) where the client couldn't find the file it "had" been reading (by inode and offset). John

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 | 2 + src/Makefile.am | 4 +- src/libvirt_private.syms | 8 ++ src/logging/log_daemon_dispatch.c | 7 + src/logging/log_handler.c | 7 +- src/logging/log_manager.c | 283 ++++++++++++++++++++++++++++++++++++++ src/logging/log_manager.h | 61 ++++++++ src/logging/log_protocol.x | 1 + 8 files changed, 370 insertions(+), 3 deletions(-) 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..4dc1476 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -80,7 +80,9 @@ src/locking/lock_manager.c src/locking/sanlock_helper.c src/logging/log_daemon.c src/logging/log_daemon_config.c +src/logging/log_daemon_dispatch.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 c658204..3c98c38 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 f10493e..b2e8096 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1003,6 +1003,14 @@ virLockManagerPluginUsesState; virLockManagerRelease; +# logging/log_manager.h +virLogManagerDomainGetLogFilePosition; +virLogManagerDomainOpenLogFile; +virLogManagerDomainReadLogFile; +virLogManagerFree; +virLogManagerNew; + + # nodeinfo.h nodeAllocPages; nodeCapsInitNUMA; diff --git a/src/logging/log_daemon_dispatch.c b/src/logging/log_daemon_dispatch.c index f612be1..203fdc4 100644 --- a/src/logging/log_daemon_dispatch.c +++ b/src/logging/log_daemon_dispatch.c @@ -116,6 +116,13 @@ virLogManagerProtocolDispatchDomainReadLogFile(virNetServerPtr server ATTRIBUTE_ int rv = -1; char *data; + if (args->maxlen > VIR_LOG_MANAGER_PROTOCOL_STRING_MAX) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Requested data len %zu is larger than maximum %d"), + args->maxlen, VIR_LOG_MANAGER_PROTOCOL_STRING_MAX); + goto cleanup; + } + if ((data = virLogHandlerDomainReadLogFile(virLogDaemonGetHandler(logDaemon), args->driver, (unsigned char *)args->dom.uuid, diff --git a/src/logging/log_handler.c b/src/logging/log_handler.c index 8853fd0..1465c5a 100644 --- a/src/logging/log_handler.c +++ b/src/logging/log_handler.c @@ -442,6 +442,7 @@ char *virLogHandlerDomainReadLogFile(virLogHandlerPtr handler, char *path; virRotatingFileReaderPtr file = NULL; char *data = NULL; + ssize_t got; if (!(path = virLogHandlerGetLogFilePathForDomain(handler, driver, @@ -455,11 +456,13 @@ char *virLogHandlerDomainReadLogFile(virLogHandlerPtr handler, if (virRotatingFileReaderSeek(file, inode, offset) < 0) goto error; - if (VIR_ALLOC_N(data, maxlen) < 0) + if (VIR_ALLOC_N(data, maxlen + 1) < 0) goto error; - if (virRotatingFileReaderConsume(file, data, maxlen) < 0) + got = virRotatingFileReaderConsume(file, data, maxlen); + if (got < 0) goto error; + data[got] = '\0'; virRotatingFileReaderFree(file); return data; diff --git a/src/logging/log_manager.c b/src/logging/log_manager.c new file mode 100644 index 0000000..27941ea --- /dev/null +++ b/src/logging/log_manager.c @@ -0,0 +1,283 @@ +/* + * 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, + ino_t *inode, + off_t *offset) +{ + struct virLogManagerProtocolDomainOpenLogFileArgs args; + struct virLogManagerProtocolDomainOpenLogFileRet ret; + int *fdout = NULL; + size_t fdoutlen = 0; + int rv = -1; + + memset(&args, 0, sizeof(args)); + memset(&ret, 0, sizeof(ret)); + + 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_virLogManagerProtocolDomainOpenLogFileRet, &ret) < 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; + } + + *inode = ret.pos.inode; + *offset = ret.pos.offset; + + rv = fdout[0]; + cleanup: + if (rv < 0) { + while (fdoutlen) + VIR_FORCE_CLOSE(fdout[--fdoutlen]); + } + VIR_FREE(fdout); + + return rv; +} + + +int virLogManagerDomainGetLogFilePosition(virLogManagerPtr mgr, + const char *driver, + const unsigned char *domuuid, + const char *domname, + unsigned int flags, + ino_t *inode, + off_t *offset) +{ + struct virLogManagerProtocolDomainGetLogFilePositionArgs args; + struct virLogManagerProtocolDomainGetLogFilePositionRet ret; + int rv = -1; + + memset(&args, 0, sizeof(args)); + memset(&ret, 0, sizeof(ret)); + + 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_GET_LOG_FILE_POSITION, + 0, NULL, NULL, NULL, + (xdrproc_t)xdr_virLogManagerProtocolDomainGetLogFilePositionArgs, &args, + (xdrproc_t)xdr_virLogManagerProtocolDomainGetLogFilePositionRet, &ret) < 0) + goto cleanup; + + *inode = ret.pos.inode; + *offset = ret.pos.offset; + + rv = 0; + cleanup: + return rv; +} + + +char *virLogManagerDomainReadLogFile(virLogManagerPtr mgr, + const char *driver, + const unsigned char *domuuid, + const char *domname, + ino_t inode, + off_t offset, + size_t maxlen, + unsigned int flags) +{ + struct virLogManagerProtocolDomainReadLogFileArgs args; + struct virLogManagerProtocolDomainReadLogFileRet ret; + int *fdout = NULL; + size_t fdoutlen = 0; + char *rv = NULL; + + memset(&args, 0, sizeof(args)); + memset(&ret, 0, sizeof(ret)); + + args.driver = (char *)driver; + memcpy(args.dom.uuid, domuuid, VIR_UUID_BUFLEN); + args.dom.name = (char *)domname; + args.flags = flags; + args.pos.inode = inode; + args.pos.offset = offset; + args.maxlen = maxlen; + + if (virNetClientProgramCall(mgr->program, + mgr->client, + mgr->serial++, + VIR_LOG_MANAGER_PROTOCOL_PROC_DOMAIN_READ_LOG_FILE, + 0, NULL, &fdoutlen, &fdout, + (xdrproc_t)xdr_virLogManagerProtocolDomainReadLogFileArgs, &args, + (xdrproc_t)xdr_virLogManagerProtocolDomainReadLogFileRet, &ret) < 0) + goto cleanup; + + rv = ret.data; + cleanup: + return rv; +} diff --git a/src/logging/log_manager.h b/src/logging/log_manager.h new file mode 100644 index 0000000..3dfe516 --- /dev/null +++ b/src/logging/log_manager.h @@ -0,0 +1,61 @@ +/* + * 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, + ino_t *inode, + off_t *offset); + +int virLogManagerDomainGetLogFilePosition(virLogManagerPtr mgr, + const char *driver, + const unsigned char *domuuid, + const char *domname, + unsigned int flags, + ino_t *inode, + off_t *offset); + +char *virLogManagerDomainReadLogFile(virLogManagerPtr mgr, + const char *driver, + const unsigned char *domuuid, + const char *domname, + ino_t inode, + off_t offset, + size_t maxlen, + unsigned int flags); + +#endif /* __VIR_LOG_MANAGER_H__ */ diff --git a/src/logging/log_protocol.x b/src/logging/log_protocol.x index 2702beb..de57c69 100644 --- a/src/logging/log_protocol.x +++ b/src/logging/log_protocol.x @@ -57,6 +57,7 @@ struct virLogManagerProtocolDomainReadLogFileArgs { virLogManagerProtocolDomain dom; virLogManagerProtocolLogFilePosition pos; unsigned hyper maxlen; + unsigned int flags; }; struct virLogManagerProtocolDomainReadLogFileRet { -- 2.5.0

On 11/12/2015 12:19 PM, Daniel P. Berrange wrote:
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 | 2 + src/Makefile.am | 4 +- src/libvirt_private.syms | 8 ++ src/logging/log_daemon_dispatch.c | 7 + src/logging/log_handler.c | 7 +- src/logging/log_manager.c | 283 ++++++++++++++++++++++++++++++++++++++ src/logging/log_manager.h | 61 ++++++++ src/logging/log_protocol.x | 1 + 8 files changed, 370 insertions(+), 3 deletions(-) create mode 100644 src/logging/log_manager.c create mode 100644 src/logging/log_manager.h
[...]
diff --git a/src/logging/log_daemon_dispatch.c b/src/logging/log_daemon_dispatch.c index f612be1..203fdc4 100644 --- a/src/logging/log_daemon_dispatch.c +++ b/src/logging/log_daemon_dispatch.c @@ -116,6 +116,13 @@ virLogManagerProtocolDispatchDomainReadLogFile(virNetServerPtr server ATTRIBUTE_ int rv = -1; char *data;
+ if (args->maxlen > VIR_LOG_MANAGER_PROTOCOL_STRING_MAX) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Requested data len %zu is larger than maximum %d"), + args->maxlen, VIR_LOG_MANAGER_PROTOCOL_STRING_MAX); + goto cleanup; + } +
This feels like it should have been in prior patch...
if ((data = virLogHandlerDomainReadLogFile(virLogDaemonGetHandler(logDaemon), args->driver, (unsigned char *)args->dom.uuid, diff --git a/src/logging/log_handler.c b/src/logging/log_handler.c index 8853fd0..1465c5a 100644 --- a/src/logging/log_handler.c +++ b/src/logging/log_handler.c @@ -442,6 +442,7 @@ char *virLogHandlerDomainReadLogFile(virLogHandlerPtr handler, char *path; virRotatingFileReaderPtr file = NULL; char *data = NULL; + ssize_t got;
if (!(path = virLogHandlerGetLogFilePathForDomain(handler, driver, @@ -455,11 +456,13 @@ char *virLogHandlerDomainReadLogFile(virLogHandlerPtr handler, if (virRotatingFileReaderSeek(file, inode, offset) < 0) goto error;
- if (VIR_ALLOC_N(data, maxlen) < 0) + if (VIR_ALLOC_N(data, maxlen + 1) < 0) goto error;
- if (virRotatingFileReaderConsume(file, data, maxlen) < 0) + got = virRotatingFileReaderConsume(file, data, maxlen); + if (got < 0) goto error; + data[got] = '\0';
Perhaps another prior patch..
virRotatingFileReaderFree(file); return data;
[...] Nothing jumped out at me in log_manager.{ch}
diff --git a/src/logging/log_protocol.x b/src/logging/log_protocol.x index 2702beb..de57c69 100644 --- a/src/logging/log_protocol.x +++ b/src/logging/log_protocol.x @@ -57,6 +57,7 @@ struct virLogManagerProtocolDomainReadLogFileArgs { virLogManagerProtocolDomain dom; virLogManagerProtocolLogFilePosition pos; unsigned hyper maxlen; + unsigned int flags;
should this perhaps have been in the prior patch?
};
struct virLogManagerProtocolDomainReadLogFileRet {
ACK - nothing major here - your call on whether things should move. John

The rename operation only works on inactive virtual machines, but it none the less writes to the log file used by the QEMU processes. This log file is not intended to provide a general purpose audit trail of operations performed on VMs. The audit subsystem has recording of important operations. If we want to extend that to cover all significant public APIs that is a valid thing to consider, but we shouldn't arbitrarily log specific APIs into the QEMU log file in the meantime. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu_driver.c | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 92a9961..d537523 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -19909,10 +19909,6 @@ static int qemuDomainRename(virDomainPtr dom, virObjectEventPtr event_new = NULL; virObjectEventPtr event_old = NULL; int ret = -1; - int logfile = -1; - char ebuf[1024]; - char *timestamp; - char *rename_log_msg = NULL; char *new_dom_name = NULL; char *old_dom_name = NULL; char *old_dom_cfg_file = NULL; @@ -19978,11 +19974,6 @@ static int qemuDomainRename(virDomainPtr dom, if (VIR_STRDUP(new_dom_name, new_name) < 0) goto endjob; - if (virAsprintf(&rename_log_msg, ": domain %s has been renamed to %s\n", - vm->def->name, new_name) < 0) { - goto endjob; - } - if (!(old_dom_cfg_file = virDomainConfigFile(cfg->configDir, vm->def->name))) { goto endjob; @@ -19991,9 +19982,6 @@ static int qemuDomainRename(virDomainPtr dom, if (virDomainObjListRenameAddNew(driver->domains, vm, new_name) < 0) goto endjob; - if ((logfile = qemuDomainCreateLog(driver, vm, true)) < 0) - goto rollback; - event_old = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_UNDEFINED, VIR_DOMAIN_EVENT_UNDEFINED_RENAMED); @@ -20021,17 +20009,6 @@ static int qemuDomainRename(virDomainPtr dom, VIR_DOMAIN_EVENT_DEFINED, VIR_DOMAIN_EVENT_DEFINED_RENAMED); - /* Write message to the log. */ - if ((timestamp = virTimeStringNow()) != NULL) { - if (safewrite(logfile, timestamp, strlen(timestamp)) < 0 || - safewrite(logfile, rename_log_msg, - strlen(rename_log_msg)) < 0) { - VIR_WARN("Unable to write timestamp to logfile: %s", - virStrerror(errno, ebuf, sizeof(ebuf))); - } - VIR_FREE(timestamp); - } - /* Success, domain has been renamed. */ ret = 0; @@ -20039,15 +20016,10 @@ static int qemuDomainRename(virDomainPtr dom, qemuDomainObjEndJob(driver, vm); cleanup: - if (VIR_CLOSE(logfile) < 0) { - VIR_WARN("Unable to close logfile: %s", - virStrerror(errno, ebuf, sizeof(ebuf))); - } virDomainObjEndAPI(&vm); VIR_FREE(old_dom_cfg_file); VIR_FREE(old_dom_name); VIR_FREE(new_dom_name); - VIR_FREE(rename_log_msg); qemuDomainEventQueue(driver, event_old); qemuDomainEventQueue(driver, event_new); virObjectUnref(cfg); -- 2.5.0

On 11/12/2015 12:19 PM, Daniel P. Berrange wrote:
The rename operation only works on inactive virtual machines, but it none the less writes to the log file used by the QEMU processes. This log file is not intended to provide a general purpose audit trail of operations performed on VMs. The audit subsystem has recording of important operations. If we want to extend that to cover all significant public APIs that is a valid thing to consider, but we shouldn't arbitrarily log specific APIs into the QEMU log file in the meantime.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu_driver.c | 28 ---------------------------- 1 file changed, 28 deletions(-)
Seems reasonable. Of course one could have "renamed" the log file to use the new name in order to keep all the history of the same machine (the uuid seems to remain constant). ACK - John

There are two pretty similar functions qemuProcessReadLog and qemuProcessReadChildErrors. Both read from the QEMU log file and try to strip out libvirt messages. The latter than reports an error, while the former lets the callers report an error. Re-write qemuProcessReadLog so that it uses a single read into a dynamically allocated buffer. Then introduce a new qemuProcessReportLogError that calls qemuProcessReadLog and reports an error. Convert all callers to use qemuProcessReportLogError. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu_domain.c | 24 +----- src/qemu/qemu_domain.h | 2 +- src/qemu/qemu_migration.c | 2 +- src/qemu/qemu_monitor.c | 58 +++----------- src/qemu/qemu_monitor.h | 2 +- src/qemu/qemu_process.c | 200 ++++++++++++++++++---------------------------- src/qemu/qemu_process.h | 4 +- 7 files changed, 95 insertions(+), 197 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 416ab5b..1dd3cee 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -2297,38 +2297,16 @@ qemuDomainCreateLog(virQEMUDriverPtr driver, virDomainObjPtr vm, int -qemuDomainOpenLog(virQEMUDriverPtr driver, virDomainObjPtr vm, off_t pos) +qemuDomainOpenLog(virQEMUDriverPtr driver, virDomainObjPtr vm) { virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); int fd; - off_t off; - int whence; fd = qemuDomainOpenLogHelper(cfg, vm, O_RDONLY, 0); virObjectUnref(cfg); if (fd < 0) return -1; - if (pos < 0) { - off = 0; - whence = SEEK_END; - } else { - off = pos; - whence = SEEK_SET; - } - - if (lseek(fd, off, whence) < 0) { - if (whence == SEEK_END) - virReportSystemError(errno, - _("unable to seek to end of log for %s"), - vm->def->name); - else - virReportSystemError(errno, - _("unable to seek to %lld from start for %s"), - (long long)off, vm->def->name); - VIR_FORCE_CLOSE(fd); - } - return fd; } diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 4be998c..16dc93c 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -350,7 +350,7 @@ void qemuDomainObjCheckNetTaint(virQEMUDriverPtr driver, int qemuDomainCreateLog(virQEMUDriverPtr driver, virDomainObjPtr vm, bool append); -int qemuDomainOpenLog(virQEMUDriverPtr driver, virDomainObjPtr vm, off_t pos); +int qemuDomainOpenLog(virQEMUDriverPtr driver, virDomainObjPtr vm); int qemuDomainAppendLog(virQEMUDriverPtr driver, virDomainObjPtr vm, int logFD, diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 3eee3a5..475f97d 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -5849,7 +5849,7 @@ qemuMigrationFinish(virQEMUDriverPtr driver, cleanup: virPortAllocatorRelease(driver->migrationPorts, port); if (priv->mon) - qemuMonitorSetDomainLog(priv->mon, -1); + qemuMonitorSetDomainLog(priv->mon, -1, -1); VIR_FREE(priv->origname); virDomainObjEndAPI(&vm); if (mig) { diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 49d4aa2..d3f0c09 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -96,6 +96,7 @@ struct _qemuMonitor { /* Log file fd of the qemu process to dig for usable info */ int logfd; + off_t logpos; }; /** @@ -389,38 +390,6 @@ qemuMonitorOpenPty(const char *monitor) } -/* Get a possible error from qemu's log. This function closes the - * corresponding log fd */ -static char * -qemuMonitorGetErrorFromLog(qemuMonitorPtr mon) -{ - int len; - char *logbuf = NULL; - int orig_errno = errno; - - if (mon->logfd < 0) - return NULL; - - if (VIR_ALLOC_N_QUIET(logbuf, 4096) < 0) - goto error; - - if ((len = qemuProcessReadLog(mon->logfd, logbuf, 4096 - 1, 0, true)) <= 0) - goto error; - - while (len > 0 && logbuf[len - 1] == '\n') - logbuf[--len] = '\0'; - - cleanup: - errno = orig_errno; - VIR_FORCE_CLOSE(mon->logfd); - return logbuf; - - error: - VIR_FREE(logbuf); - goto cleanup; -} - - /* This method processes data that has been received * from the monitor. Looking for async events and * replies/errors. @@ -737,25 +706,20 @@ qemuMonitorIO(int watch, int fd, int events, void *opaque) } if (error || eof) { - if (hangup) { + if (hangup && mon->logfd != -1) { /* Check if an error message from qemu is available and if so, use * it to overwrite the actual message. It's done only in early * startup phases or during incoming migration when the message * from qemu is certainly more interesting than a * "connection reset by peer" message. */ - char *qemuMessage; - - if ((qemuMessage = qemuMonitorGetErrorFromLog(mon))) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("early end of file from monitor: " - "possible problem:\n%s"), - qemuMessage); - virCopyLastError(&mon->lastError); - virResetLastError(); - } - - VIR_FREE(qemuMessage); + qemuProcessReportLogError(mon->logfd, + mon->logpos, + _("early end of file from monitor, " + "possible problem")); + VIR_FORCE_CLOSE(mon->logfd); + virCopyLastError(&mon->lastError); + virResetLastError(); } if (mon->lastError.code != VIR_ERR_OK) { @@ -3683,9 +3647,10 @@ qemuMonitorGetDeviceAliases(qemuMonitorPtr mon, * * @mon: Monitor object to set the log file reading on * @logfd: File descriptor of the already open log file + * @pos: position to read errors from */ int -qemuMonitorSetDomainLog(qemuMonitorPtr mon, int logfd) +qemuMonitorSetDomainLog(qemuMonitorPtr mon, int logfd, off_t pos) { VIR_FORCE_CLOSE(mon->logfd); if (logfd >= 0 && @@ -3693,6 +3658,7 @@ qemuMonitorSetDomainLog(qemuMonitorPtr mon, int logfd) virReportSystemError(errno, "%s", _("failed to duplicate log fd")); return -1; } + mon->logpos = pos; return 0; } diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index 2ce3958..a2a9a77 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -892,7 +892,7 @@ int qemuMonitorDetachCharDev(qemuMonitorPtr mon, int qemuMonitorGetDeviceAliases(qemuMonitorPtr mon, char ***aliases); -int qemuMonitorSetDomainLog(qemuMonitorPtr mon, int logfd); +int qemuMonitorSetDomainLog(qemuMonitorPtr mon, int logfd, off_t pos); int qemuMonitorGetGuestCPU(qemuMonitorPtr mon, virArch arch, diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 69a0f97..c09e9dc 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -1549,7 +1549,7 @@ static qemuMonitorCallbacks monitorCallbacks = { static int qemuConnectMonitor(virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob, - int logfd) + int logfd, off_t pos) { qemuDomainObjPrivatePtr priv = vm->privateData; int ret = -1; @@ -1575,8 +1575,8 @@ qemuConnectMonitor(virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob, &monitorCallbacks, driver); - if (mon) - ignore_value(qemuMonitorSetDomainLog(mon, logfd)); + if (mon && logfd != -1 && pos != -1) + ignore_value(qemuMonitorSetDomainLog(mon, logfd, pos)); virObjectLock(vm); virObjectUnref(vm); @@ -1630,111 +1630,76 @@ qemuConnectMonitor(virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob, /** * qemuProcessReadLog: Read log file of a qemu VM * @fd: File descriptor of the log file - * @buf: buffer to store the read messages - * @buflen: allocated space available in @buf + * @msg: pointer to buffer to store the read messages in * @off: Offset to start reading from - * @skipchar: Skip messages about created character devices * * Reads log of a qemu VM. Skips messages not produced by qemu or irrelevant - * messages. Returns length of the message stored in @buf, or -1 on error. + * messages. Returns returns 0 on success or -1 on error */ -int -qemuProcessReadLog(int fd, char *buf, int buflen, int off, bool skipchar) +static int +qemuProcessReadLog(int fd, off_t offset, char **msg) { - char *filter_next = buf; - ssize_t bytes; + char *buf; + size_t buflen = 1024 * 128; + ssize_t got; char *eol; + char *filter_next; - while (off < buflen - 1) { - bytes = saferead(fd, buf + off, buflen - off - 1); - if (bytes < 0) - return -1; - - off += bytes; - buf[off] = '\0'; + /* Best effort jump to start of messages */ + ignore_value(lseek(fd, offset, SEEK_SET)); - if (bytes == 0) - break; + if (VIR_ALLOC_N(buf, buflen) < 0) + return -1; - /* Filter out debug messages from intermediate libvirt process */ - while ((eol = strchr(filter_next, '\n'))) { - *eol = '\0'; - if (virLogProbablyLogMessage(filter_next) || - (skipchar && - STRPREFIX(filter_next, "char device redirected to"))) { - memmove(filter_next, eol + 1, off - (eol - buf)); - off -= eol + 1 - filter_next; - } else { - filter_next = eol + 1; - *eol = '\n'; - } - } + got = saferead(fd, buf, buflen - 1); + if (got < 0) { + virReportSystemError(errno, "%s", + _("Unable to read from log file")); + return -1; } - return off; -} - -/* - * Read domain log and probably overwrite error if there's one in - * the domain log file. This function exists to cover the small - * window between fork() and exec() during which child may fail - * by libvirt's hand, e.g. placing onto a NUMA node failed. - */ -static int -qemuProcessReadChildErrors(virQEMUDriverPtr driver, - virDomainObjPtr vm, - off_t originalOff) -{ - int ret = -1; - int logfd; - off_t off = 0; - ssize_t bytes; - char buf[1024] = {0}; - char *eol, *filter_next = buf; - - if ((logfd = qemuDomainOpenLog(driver, vm, originalOff)) < 0) - goto cleanup; - - while (off < sizeof(buf) - 1) { - bytes = saferead(logfd, buf + off, sizeof(buf) - off - 1); - if (bytes < 0) { - VIR_WARN("unable to read from log file: %s", - virStrerror(errno, buf, sizeof(buf))); - goto cleanup; - } + buf[got] = '\0'; - off += bytes; - buf[off] = '\0'; - - if (bytes == 0) - break; - - while ((eol = strchr(filter_next, '\n'))) { - *eol = '\0'; - if (STRPREFIX(filter_next, "libvirt: ")) { - filter_next = eol + 1; - *eol = '\n'; - break; - } else { - memmove(filter_next, eol + 1, off - (eol - buf)); - off -= eol + 1 - filter_next; - } + /* Filter out debug messages from intermediate libvirt process */ + filter_next = buf; + while ((eol = strchr(filter_next, '\n'))) { + *eol = '\0'; + if (virLogProbablyLogMessage(filter_next) || + STRPREFIX(filter_next, "char device redirected to")) { + size_t skip = (eol + 1) - filter_next; + memmove(filter_next, eol + 1, (got - skip) + 1); + got -= skip; + } else { + filter_next = eol + 1; + *eol = '\n'; } } - if (off > 0) { - /* Found an error in the log. Report it */ - virResetLastError(); - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Process exited prior to exec: %s"), - buf); + if (buf[got - 1] == '\n') { + buf[got - 1] = '\0'; + got--; } + VIR_SHRINK_N(buf, buflen, buflen - got - 1); + *msg = buf; + return 0; +} - ret = 0; - cleanup: - VIR_FORCE_CLOSE(logfd); - return ret; +int +qemuProcessReportLogError(int logfd, + off_t offset, + const char *msgprefix) +{ + char *logmsg = NULL; + + if (qemuProcessReadLog(logfd, offset, &logmsg) < 0) + return -1; + + virResetLastError(); + virReportError(VIR_ERR_INTERNAL_ERROR, + _("%s: %s"), msgprefix, logmsg); + VIR_FREE(logmsg); + return 0; } @@ -1944,20 +1909,17 @@ qemuProcessWaitForMonitor(virQEMUDriverPtr driver, virQEMUCapsPtr qemuCaps, off_t pos) { - char *buf = NULL; - size_t buf_size = 4096; /* Plenty of space to get startup greeting */ - int logfd = -1; int ret = -1; virHashTablePtr info = NULL; qemuDomainObjPrivatePtr priv; + int logfd = -1; - if (pos != -1 && - (logfd = qemuDomainOpenLog(driver, vm, pos)) < 0) - return -1; - + if (pos != (off_t)-1 && + (logfd = qemuDomainOpenLog(driver, vm)) < 0) + goto cleanup; VIR_DEBUG("Connect monitor to %p '%s'", vm, vm->def->name); - if (qemuConnectMonitor(driver, vm, asyncJob, logfd) < 0) + if (qemuConnectMonitor(driver, vm, asyncJob, logfd, pos) < 0) goto cleanup; /* Try to get the pty path mappings again via the monitor. This is much more @@ -1985,31 +1947,14 @@ qemuProcessWaitForMonitor(virQEMUDriverPtr driver, cleanup: virHashFree(info); - if (pos != -1 && kill(vm->pid, 0) == -1 && errno == ESRCH) { - /* VM is dead, any other error raised in the interim is probably - * not as important as the qemu cmdline output */ - if (VIR_ALLOC_N(buf, buf_size) < 0) - goto closelog; - - /* best effort seek - we need to reset to the original position, so that - * a possible read of the fd in the monitor code doesn't influence this - * error delivery option */ - ignore_value(lseek(logfd, pos, SEEK_SET)); - qemuProcessReadLog(logfd, buf, buf_size - 1, 0, true); - virReportError(VIR_ERR_INTERNAL_ERROR, - _("process exited while connecting to monitor: %s"), - buf); + if (pos != (off_t)-1 && kill(vm->pid, 0) == -1 && errno == ESRCH) { + qemuProcessReportLogError(logfd, pos, + _("process exited while connecting to monitor")); ret = -1; } - closelog: - if (VIR_CLOSE(logfd) < 0) { - char ebuf[1024]; - VIR_WARN("Unable to close logfile: %s", - virStrerror(errno, ebuf, sizeof(ebuf))); - } - VIR_FREE(buf); + VIR_FORCE_CLOSE(logfd); return ret; } @@ -3557,7 +3502,7 @@ qemuProcessReconnect(void *opaque) VIR_DEBUG("Reconnect monitor to %p '%s'", obj, obj->def->name); /* XXX check PID liveliness & EXE path */ - if (qemuConnectMonitor(driver, obj, QEMU_ASYNC_JOB_NONE, -1) < 0) + if (qemuConnectMonitor(driver, obj, QEMU_ASYNC_JOB_NONE, -1, -1) < 0) goto error; /* Failure to connect to agent shouldn't be fatal */ @@ -4577,9 +4522,11 @@ int qemuProcessStart(virConnectPtr conn, qemuDomainObjCheckTaint(driver, vm, logfile); - if ((pos = lseek(logfile, 0, SEEK_END)) < 0) + if ((pos = lseek(logfile, 0, SEEK_END)) < 0) { VIR_WARN("Unable to seek to end of logfile: %s", virStrerror(errno, ebuf, sizeof(ebuf))); + pos = 0; + } VIR_DEBUG("Clear emulator capabilities: %d", cfg->clearEmulatorCapabilities); @@ -4673,7 +4620,12 @@ int qemuProcessStart(virConnectPtr conn, VIR_DEBUG("Waiting for handshake from child"); if (virCommandHandshakeWait(cmd) < 0) { /* Read errors from child that occurred between fork and exec. */ - qemuProcessReadChildErrors(driver, vm, pos); + int logfd = qemuDomainOpenLog(driver, vm); + if (logfd >= 0) { + qemuProcessReportLogError(logfd, pos, + _("Process exited prior to exec")); + VIR_FORCE_CLOSE(logfd); + } goto error; } @@ -4896,7 +4848,7 @@ int qemuProcessStart(virConnectPtr conn, /* Keep watching qemu log for errors during incoming migration, otherwise * unset reporting errors from qemu log. */ if (!migrateFrom) - qemuMonitorSetDomainLog(priv->mon, -1); + qemuMonitorSetDomainLog(priv->mon, -1, -1); ret = 0; @@ -4915,7 +4867,7 @@ int qemuProcessStart(virConnectPtr conn, * if we failed to initialize the now running VM. kill it off and * pretend we never started it */ if (priv->mon) - qemuMonitorSetDomainLog(priv->mon, -1); + qemuMonitorSetDomainLog(priv->mon, -1, -1); qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED, stop_flags); goto cleanup; diff --git a/src/qemu/qemu_process.h b/src/qemu/qemu_process.h index d40f68d..2df5c4d 100644 --- a/src/qemu/qemu_process.h +++ b/src/qemu/qemu_process.h @@ -100,7 +100,9 @@ int qemuProcessAutoDestroyRemove(virQEMUDriverPtr driver, bool qemuProcessAutoDestroyActive(virQEMUDriverPtr driver, virDomainObjPtr vm); -int qemuProcessReadLog(int fd, char *buf, int buflen, int off, bool skipchar); +int qemuProcessReportLogError(int fd, + off_t offset, + const char *msgprefix); int qemuProcessSetSchedParams(int id, pid_t pid, size_t nsp, virDomainThreadSchedParamPtr sp); -- 2.5.0

On 11/12/2015 12:19 PM, Daniel P. Berrange wrote:
There are two pretty similar functions qemuProcessReadLog and qemuProcessReadChildErrors. Both read from the QEMU log file and try to strip out libvirt messages. The latter than reports
s/than/then
an error, while the former lets the callers report an error.
Re-write qemuProcessReadLog so that it uses a single read into a dynamically allocated buffer. Then introduce a new qemuProcessReportLogError that calls qemuProcessReadLog and reports an error.
Convert all callers to use qemuProcessReportLogError.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu_domain.c | 24 +----- src/qemu/qemu_domain.h | 2 +- src/qemu/qemu_migration.c | 2 +- src/qemu/qemu_monitor.c | 58 +++----------- src/qemu/qemu_monitor.h | 2 +- src/qemu/qemu_process.c | 200 ++++++++++++++++++---------------------------- src/qemu/qemu_process.h | 4 +- 7 files changed, 95 insertions(+), 197 deletions(-)
[...]
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 69a0f97..c09e9dc 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -1549,7 +1549,7 @@ static qemuMonitorCallbacks monitorCallbacks = {
static int qemuConnectMonitor(virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob, - int logfd) + int logfd, off_t pos) { qemuDomainObjPrivatePtr priv = vm->privateData; int ret = -1; @@ -1575,8 +1575,8 @@ qemuConnectMonitor(virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob, &monitorCallbacks, driver);
- if (mon) - ignore_value(qemuMonitorSetDomainLog(mon, logfd)); + if (mon && logfd != -1 && pos != -1) + ignore_value(qemuMonitorSetDomainLog(mon, logfd, pos));
virObjectLock(vm); virObjectUnref(vm); @@ -1630,111 +1630,76 @@ qemuConnectMonitor(virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob, /** * qemuProcessReadLog: Read log file of a qemu VM * @fd: File descriptor of the log file - * @buf: buffer to store the read messages - * @buflen: allocated space available in @buf + * @msg: pointer to buffer to store the read messages in
msg after off? (not that it matters that much)
* @off: Offset to start reading from - * @skipchar: Skip messages about created character devices * * Reads log of a qemu VM. Skips messages not produced by qemu or irrelevant - * messages. Returns length of the message stored in @buf, or -1 on error. + * messages. Returns returns 0 on success or -1 on error */ -int -qemuProcessReadLog(int fd, char *buf, int buflen, int off, bool skipchar) +static int +qemuProcessReadLog(int fd, off_t offset, char **msg) { - char *filter_next = buf; - ssize_t bytes; + char *buf; + size_t buflen = 1024 * 128; + ssize_t got; char *eol; + char *filter_next;
- while (off < buflen - 1) { - bytes = saferead(fd, buf + off, buflen - off - 1); - if (bytes < 0) - return -1; - - off += bytes; - buf[off] = '\0'; + /* Best effort jump to start of messages */ + ignore_value(lseek(fd, offset, SEEK_SET));
- if (bytes == 0) - break; + if (VIR_ALLOC_N(buf, buflen) < 0) + return -1;
- /* Filter out debug messages from intermediate libvirt process */ - while ((eol = strchr(filter_next, '\n'))) { - *eol = '\0'; - if (virLogProbablyLogMessage(filter_next) || - (skipchar && - STRPREFIX(filter_next, "char device redirected to"))) { - memmove(filter_next, eol + 1, off - (eol - buf)); - off -= eol + 1 - filter_next; - } else { - filter_next = eol + 1; - *eol = '\n'; - } - } + got = saferead(fd, buf, buflen - 1); + if (got < 0) { + virReportSystemError(errno, "%s", + _("Unable to read from log file"));
I know (because I ran Coverity on all the patches) that a future patch changes this, but buf is leaked here - at least for a few patches.
+ return -1; }
Additionally, theoretically 'got' could be 0 here
- return off; -} - -/* - * Read domain log and probably overwrite error if there's one in - * the domain log file. This function exists to cover the small - * window between fork() and exec() during which child may fail - * by libvirt's hand, e.g. placing onto a NUMA node failed. - */ -static int -qemuProcessReadChildErrors(virQEMUDriverPtr driver, - virDomainObjPtr vm, - off_t originalOff) -{ - int ret = -1; - int logfd; - off_t off = 0; - ssize_t bytes; - char buf[1024] = {0}; - char *eol, *filter_next = buf; - - if ((logfd = qemuDomainOpenLog(driver, vm, originalOff)) < 0) - goto cleanup; - - while (off < sizeof(buf) - 1) { - bytes = saferead(logfd, buf + off, sizeof(buf) - off - 1); - if (bytes < 0) { - VIR_WARN("unable to read from log file: %s", - virStrerror(errno, buf, sizeof(buf))); - goto cleanup; - } + buf[got] = '\0';
- off += bytes; - buf[off] = '\0'; - - if (bytes == 0) - break; - - while ((eol = strchr(filter_next, '\n'))) { - *eol = '\0'; - if (STRPREFIX(filter_next, "libvirt: ")) { - filter_next = eol + 1; - *eol = '\n'; - break; - } else { - memmove(filter_next, eol + 1, off - (eol - buf)); - off -= eol + 1 - filter_next; - } + /* Filter out debug messages from intermediate libvirt process */ + filter_next = buf; + while ((eol = strchr(filter_next, '\n'))) { + *eol = '\0'; + if (virLogProbablyLogMessage(filter_next) || + STRPREFIX(filter_next, "char device redirected to")) { + size_t skip = (eol + 1) - filter_next; + memmove(filter_next, eol + 1, (got - skip) + 1); + got -= skip; + } else { + filter_next = eol + 1; + *eol = '\n'; } }
Coverity also has a false positive here w/r/t filter_next - it claims filter_next (because it pointed to buf) is leaked when the code returns. Seems it could have something to do with when got == 1, although I don't see it. FWIW: the Coverity notes: (9) Event cond_false: Condition "eol = strchr(filter_next, 10)", taking false branch then (11) Event cond_true: Condition "buf[got - 1] == 10", taking true branch So that says, 'buf' (filter_next) didn't find '\n' in the strchr() call (e.g. taking false branch), then somehow buf[got - 1] == '\n'; If I set filter_next to NULL right after the while loop there's no error (hence my feeling on a false positive). An ACK mainly because I think what's being done is fine... John
- if (off > 0) { - /* Found an error in the log. Report it */ - virResetLastError(); - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Process exited prior to exec: %s"), - buf); + if (buf[got - 1] == '\n') { + buf[got - 1] = '\0'; + got--; } + VIR_SHRINK_N(buf, buflen, buflen - got - 1); + *msg = buf; + return 0; +}
- ret = 0;
- cleanup: - VIR_FORCE_CLOSE(logfd); - return ret;
[...]

Introduce a qemuDomainLogContext object to encapsulate handling of I/O to/from the domain log file. This will hide details of the log file implementation from the rest of the driver, making it easier to introduce support for virtlogd later. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu_domain.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_domain.h | 22 ++++++ 2 files changed, 200 insertions(+) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 1dd3cee..e92f8b4 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 "viratomic.h" #include "storage/storage_driver.h" @@ -76,6 +77,13 @@ VIR_ENUM_IMPL(qemuDomainAsyncJob, QEMU_ASYNC_JOB_LAST, ); +struct _qemuDomainLogContext { + int refs; + int writefd; + int readfd; + off_t pos; +}; + const char * qemuDomainAsyncJobPhaseToString(qemuDomainAsyncJob job, int phase ATTRIBUTE_UNUSED) @@ -2228,6 +2236,176 @@ void qemuDomainObjCheckNetTaint(virQEMUDriverPtr driver, } +qemuDomainLogContextPtr qemuDomainLogContextNew(virQEMUDriverPtr driver, + virDomainObjPtr vm, + qemuDomainLogContextMode mode) +{ + virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); + qemuDomainLogContextPtr ctxt = NULL; + char *logfile = NULL; + + if (VIR_ALLOC(ctxt) < 0) + goto error; + + ctxt->writefd = -1; + ctxt->readfd = -1; + virAtomicIntSet(&ctxt->refs, 1); + + if (virAsprintf(&logfile, "%s/%s.log", cfg->logDir, vm->def->name) < 0) + goto error; + + if ((ctxt->writefd = open(logfile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR)) < 0) { + virReportSystemError(errno, _("failed to create logfile %s"), + logfile); + goto error; + } + if (virSetCloseExec(ctxt->writefd) < 0) { + virReportSystemError(errno, _("failed to set close-on-exec flag on %s"), + logfile); + goto error; + } + + /* For unprivileged startup we must truncate the file since + * we can't rely on logrotate. We don't use O_TRUNC since + * it is better for SELinux policy if we truncate afterwards */ + if (mode == QEMU_DOMAIN_LOG_CONTEXT_MODE_START && + !virQEMUDriverIsPrivileged(driver) && + ftruncate(ctxt->writefd, 0) < 0) { + virReportSystemError(errno, _("failed to truncate %s"), + logfile); + goto error; + } + + if (mode == QEMU_DOMAIN_LOG_CONTEXT_MODE_START) { + if ((ctxt->readfd = open(logfile, O_RDONLY, S_IRUSR | S_IWUSR)) < 0) { + virReportSystemError(errno, _("failed to open logfile %s"), + logfile); + goto error; + } + if (virSetCloseExec(ctxt->readfd) < 0) { + virReportSystemError(errno, _("failed to set close-on-exec flag on %s"), + logfile); + goto error; + } + } + + virObjectUnref(cfg); + return ctxt; + + error: + virObjectUnref(cfg); + qemuDomainLogContextFree(ctxt); + return NULL; +} + + +int qemuDomainLogContextWrite(qemuDomainLogContextPtr ctxt, + const char *fmt, ...) +{ + va_list argptr; + char *message = NULL; + int ret = -1; + + va_start(argptr, fmt); + + if (virVasprintf(&message, fmt, argptr) < 0) + goto cleanup; + if (lseek(ctxt->writefd, 0, SEEK_END) < 0) { + virReportSystemError(errno, "%s", + _("Unable to see to end of domain logfile")); + goto cleanup; + } + if (safewrite(ctxt->writefd, message, strlen(message)) < 0) { + virReportSystemError(errno, "%s", + _("Unable to write to domain logfile")); + goto cleanup; + } + + ret = 0; + + cleanup: + va_end(argptr); + VIR_FREE(message); + return ret; +} + + +ssize_t qemuDomainLogContextRead(qemuDomainLogContextPtr ctxt, + char **msg) +{ + char *buf; + size_t buflen = 1024 * 128; + ssize_t got; + + /* Best effort jump to start of messages */ + ignore_value(lseek(ctxt->readfd, ctxt->pos, SEEK_SET)); + + if (VIR_ALLOC_N(buf, buflen) < 0) + return -1; + + got = saferead(ctxt->readfd, buf, buflen - 1); + if (got < 0) { + virReportSystemError(errno, "%s", + _("Unable to read from log file")); + return -1; + } + + buf[got] = '\0'; + + ignore_value(VIR_REALLOC_N_QUIET(buf, got + 1)); + *msg = buf; + + return got; +} + + +int qemuDomainLogContextGetWriteFD(qemuDomainLogContextPtr ctxt) +{ + return ctxt->writefd; +} + + +int qemuDomainLogContextGetReadFD(qemuDomainLogContextPtr ctxt) +{ + return ctxt->readfd; +} + + +void qemuDomainLogContextMarkPosition(qemuDomainLogContextPtr ctxt) +{ + ctxt->pos = lseek(ctxt->writefd, 0, SEEK_END); +} + + +off_t qemuDomainLogContextGetPosition(qemuDomainLogContextPtr ctxt) +{ + return ctxt->pos; +} + + +void qemuDomainLogContextRef(qemuDomainLogContextPtr ctxt) +{ + virAtomicIntInc(&ctxt->refs); +} + + +void qemuDomainLogContextFree(qemuDomainLogContextPtr ctxt) +{ + bool lastRef; + + if (!ctxt) + return; + + lastRef = virAtomicIntDecAndTest(&ctxt->refs); + if (!lastRef) + return; + + VIR_FORCE_CLOSE(ctxt->writefd); + VIR_FORCE_CLOSE(ctxt->readfd); + VIR_FREE(ctxt); +} + + static int qemuDomainOpenLogHelper(virQEMUDriverConfigPtr cfg, virDomainObjPtr vm, diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 16dc93c..fa0b14c 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -244,6 +244,9 @@ struct qemuProcessEvent { void *data; }; +typedef struct _qemuDomainLogContext qemuDomainLogContext; +typedef qemuDomainLogContext *qemuDomainLogContextPtr; + const char *qemuDomainAsyncJobPhaseToString(qemuDomainAsyncJob job, int phase); int qemuDomainAsyncJobPhaseFromString(qemuDomainAsyncJob job, @@ -348,6 +351,25 @@ void qemuDomainObjCheckNetTaint(virQEMUDriverPtr driver, virDomainNetDefPtr net, int logFD); +typedef enum { + QEMU_DOMAIN_LOG_CONTEXT_MODE_START, + QEMU_DOMAIN_LOG_CONTEXT_MODE_ATTACH, + QEMU_DOMAIN_LOG_CONTEXT_MODE_STOP, +} qemuDomainLogContextMode; + +qemuDomainLogContextPtr qemuDomainLogContextNew(virQEMUDriverPtr driver, + virDomainObjPtr vm, + qemuDomainLogContextMode mode); +int qemuDomainLogContextWrite(qemuDomainLogContextPtr ctxt, + const char *fmt, ...) ATTRIBUTE_FMT_PRINTF(2, 3); +ssize_t qemuDomainLogContextRead(qemuDomainLogContextPtr ctxt, + char **msg); +int qemuDomainLogContextGetWriteFD(qemuDomainLogContextPtr ctxt); +int qemuDomainLogContextGetReadFD(qemuDomainLogContextPtr ctxt); +void qemuDomainLogContextMarkPosition(qemuDomainLogContextPtr ctxt); +off_t qemuDomainLogContextGetPosition(qemuDomainLogContextPtr ctxt); +void qemuDomainLogContextRef(qemuDomainLogContextPtr ctxt); +void qemuDomainLogContextFree(qemuDomainLogContextPtr ctxt); int qemuDomainCreateLog(virQEMUDriverPtr driver, virDomainObjPtr vm, bool append); int qemuDomainOpenLog(virQEMUDriverPtr driver, virDomainObjPtr vm); -- 2.5.0

On 11/12/2015 12:19 PM, Daniel P. Berrange wrote:
Introduce a qemuDomainLogContext object to encapsulate handling of I/O to/from the domain log file. This will hide details of the log file implementation from the rest of the driver, making it easier to introduce support for virtlogd later.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu_domain.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_domain.h | 22 ++++++ 2 files changed, 200 insertions(+)
[...]
+qemuDomainLogContextPtr qemuDomainLogContextNew(virQEMUDriverPtr driver, + virDomainObjPtr vm, + qemuDomainLogContextMode mode) +{ + virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); + qemuDomainLogContextPtr ctxt = NULL; + char *logfile = NULL; + + if (VIR_ALLOC(ctxt) < 0) + goto error; + + ctxt->writefd = -1; + ctxt->readfd = -1; + virAtomicIntSet(&ctxt->refs, 1); + + if (virAsprintf(&logfile, "%s/%s.log", cfg->logDir, vm->def->name) < 0) + goto error; + + if ((ctxt->writefd = open(logfile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR)) < 0) { + virReportSystemError(errno, _("failed to create logfile %s"), + logfile); + goto error; + } + if (virSetCloseExec(ctxt->writefd) < 0) { + virReportSystemError(errno, _("failed to set close-on-exec flag on %s"), + logfile); + goto error; + } + + /* For unprivileged startup we must truncate the file since + * we can't rely on logrotate. We don't use O_TRUNC since + * it is better for SELinux policy if we truncate afterwards */ + if (mode == QEMU_DOMAIN_LOG_CONTEXT_MODE_START && + !virQEMUDriverIsPrivileged(driver) && + ftruncate(ctxt->writefd, 0) < 0) { + virReportSystemError(errno, _("failed to truncate %s"), + logfile); + goto error; + } + + if (mode == QEMU_DOMAIN_LOG_CONTEXT_MODE_START) { + if ((ctxt->readfd = open(logfile, O_RDONLY, S_IRUSR | S_IWUSR)) < 0) { + virReportSystemError(errno, _("failed to open logfile %s"), + logfile); + goto error; + } + if (virSetCloseExec(ctxt->readfd) < 0) { + virReportSystemError(errno, _("failed to set close-on-exec flag on %s"), + logfile); + goto error; + } + }
should 'pos' be initialized here? Otherwise the first ContextRead would use ctxt->pos == 0
+ + virObjectUnref(cfg); + return ctxt; + + error: + virObjectUnref(cfg); + qemuDomainLogContextFree(ctxt); + return NULL; +} + +
[...]
+ +ssize_t qemuDomainLogContextRead(qemuDomainLogContextPtr ctxt, + char **msg) +{ + char *buf; + size_t buflen = 1024 * 128; + ssize_t got; + + /* Best effort jump to start of messages */ + ignore_value(lseek(ctxt->readfd, ctxt->pos, SEEK_SET)); + + if (VIR_ALLOC_N(buf, buflen) < 0) + return -1; + + got = saferead(ctxt->readfd, buf, buflen - 1); + if (got < 0) { + virReportSystemError(errno, "%s", + _("Unable to read from log file"));
Coverity points out buf is leaked.
+ return -1; + } + + buf[got] = '\0'; + + ignore_value(VIR_REALLOC_N_QUIET(buf, got + 1)); + *msg = buf; + + return got; +} + +
ACK with the obvious adjustment - not quite sure how to handle 'pos' (yet) - although perhaps future patches will allow the light to shine. John

Convert the places which create/open log files to use the new qemuDomainLogContextPtr object instead. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu_domain.c | 100 +++++------------------------------------------- src/qemu/qemu_domain.h | 2 - src/qemu/qemu_process.c | 78 +++++++++++++++++-------------------- 3 files changed, 46 insertions(+), 134 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index e92f8b4..f3bb8d4 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -2406,108 +2406,29 @@ void qemuDomainLogContextFree(qemuDomainLogContextPtr ctxt) } -static int -qemuDomainOpenLogHelper(virQEMUDriverConfigPtr cfg, - virDomainObjPtr vm, - int oflags, - mode_t mode) -{ - char *logfile; - int fd = -1; - bool trunc = false; - - if (virAsprintf(&logfile, "%s/%s.log", cfg->logDir, vm->def->name) < 0) - return -1; - - /* To make SELinux happy we always need to open in append mode. - * So we fake O_TRUNC by calling ftruncate after open instead - */ - if (oflags & O_TRUNC) { - oflags &= ~O_TRUNC; - oflags |= O_APPEND; - trunc = true; - } - - if ((fd = open(logfile, oflags, mode)) < 0) { - virReportSystemError(errno, _("failed to create logfile %s"), - logfile); - goto cleanup; - } - if (virSetCloseExec(fd) < 0) { - virReportSystemError(errno, _("failed to set close-on-exec flag on %s"), - logfile); - VIR_FORCE_CLOSE(fd); - goto cleanup; - } - if (trunc && - ftruncate(fd, 0) < 0) { - virReportSystemError(errno, _("failed to truncate %s"), - logfile); - VIR_FORCE_CLOSE(fd); - goto cleanup; - } - - cleanup: - VIR_FREE(logfile); - 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; - - ret = qemuDomainOpenLogHelper(cfg, vm, oflags, S_IRUSR | S_IWUSR); - virObjectUnref(cfg); - return ret; -} - - -int -qemuDomainOpenLog(virQEMUDriverPtr driver, virDomainObjPtr vm) -{ - virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); - int fd; - - fd = qemuDomainOpenLogHelper(cfg, vm, O_RDONLY, 0); - virObjectUnref(cfg); - if (fd < 0) - return -1; - - return fd; -} - - int qemuDomainAppendLog(virQEMUDriverPtr driver, virDomainObjPtr obj, int logFD, const char *fmt, ...) { - int fd = logFD; va_list argptr; char *message = NULL; int ret = -1; + qemuDomainLogContextPtr logCtxt = NULL; va_start(argptr, fmt); - if ((fd == -1) && - (fd = qemuDomainCreateLog(driver, obj, true)) < 0) - goto cleanup; + if (logFD == -1) { + logCtxt = qemuDomainLogContextNew(driver, obj, + QEMU_DOMAIN_LOG_CONTEXT_MODE_ATTACH); + if (!logCtxt) + goto cleanup; + logFD = qemuDomainLogContextGetWriteFD(logCtxt); + } if (virVasprintf(&message, fmt, argptr) < 0) goto cleanup; - if (safewrite(fd, message, strlen(message)) < 0) { + if (safewrite(logFD, message, strlen(message)) < 0) { virReportSystemError(errno, _("Unable to write to domain logfile %s"), obj->def->name); goto cleanup; @@ -2518,8 +2439,7 @@ int qemuDomainAppendLog(virQEMUDriverPtr driver, cleanup: va_end(argptr); - if (fd != logFD) - VIR_FORCE_CLOSE(fd); + qemuDomainLogContextFree(logCtxt); VIR_FREE(message); return ret; diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index fa0b14c..51820d8 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -371,8 +371,6 @@ off_t qemuDomainLogContextGetPosition(qemuDomainLogContextPtr ctxt); void qemuDomainLogContextRef(qemuDomainLogContextPtr ctxt); void qemuDomainLogContextFree(qemuDomainLogContextPtr ctxt); -int qemuDomainCreateLog(virQEMUDriverPtr driver, virDomainObjPtr vm, bool append); -int qemuDomainOpenLog(virQEMUDriverPtr driver, virDomainObjPtr vm); int qemuDomainAppendLog(virQEMUDriverPtr driver, virDomainObjPtr vm, int logFD, diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index c09e9dc..e433548 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -1907,16 +1907,18 @@ qemuProcessWaitForMonitor(virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob, virQEMUCapsPtr qemuCaps, - off_t pos) + qemuDomainLogContextPtr logCtxt) { int ret = -1; virHashTablePtr info = NULL; qemuDomainObjPrivatePtr priv; int logfd = -1; + off_t pos = -1; - if (pos != (off_t)-1 && - (logfd = qemuDomainOpenLog(driver, vm)) < 0) - goto cleanup; + if (logCtxt) { + logfd = qemuDomainLogContextGetReadFD(logCtxt); + pos = qemuDomainLogContextGetPosition(logCtxt); + } VIR_DEBUG("Connect monitor to %p '%s'", vm, vm->def->name); if (qemuConnectMonitor(driver, vm, asyncJob, logfd, pos) < 0) @@ -1953,9 +1955,6 @@ qemuProcessWaitForMonitor(virQEMUDriverPtr driver, ret = -1; } - - VIR_FORCE_CLOSE(logfd); - return ret; } @@ -4114,9 +4113,8 @@ int qemuProcessStart(virConnectPtr conn, { int ret = -1; int rv; - off_t pos = -1; - char ebuf[1024]; int logfile = -1; + qemuDomainLogContextPtr logCtxt = NULL; qemuDomainObjPrivatePtr priv = vm->privateData; virCommandPtr cmd = NULL; struct qemuProcessHookData hookData; @@ -4354,8 +4352,10 @@ int qemuProcessStart(virConnectPtr conn, } VIR_DEBUG("Creating domain log file"); - if ((logfile = qemuDomainCreateLog(driver, vm, false)) < 0) + if (!(logCtxt = qemuDomainLogContextNew(driver, vm, + QEMU_DOMAIN_LOG_CONTEXT_MODE_START))) goto error; + logfile = qemuDomainLogContextGetWriteFD(logCtxt); if (vm->def->virtType == VIR_DOMAIN_VIRT_KVM) { VIR_DEBUG("Checking for KVM availability"); @@ -4522,11 +4522,7 @@ int qemuProcessStart(virConnectPtr conn, qemuDomainObjCheckTaint(driver, vm, logfile); - if ((pos = lseek(logfile, 0, SEEK_END)) < 0) { - VIR_WARN("Unable to seek to end of logfile: %s", - virStrerror(errno, ebuf, sizeof(ebuf))); - pos = 0; - } + qemuDomainLogContextMarkPosition(logCtxt); VIR_DEBUG("Clear emulator capabilities: %d", cfg->clearEmulatorCapabilities); @@ -4620,11 +4616,11 @@ int qemuProcessStart(virConnectPtr conn, VIR_DEBUG("Waiting for handshake from child"); if (virCommandHandshakeWait(cmd) < 0) { /* Read errors from child that occurred between fork and exec. */ - int logfd = qemuDomainOpenLog(driver, vm); + int logfd = qemuDomainLogContextGetReadFD(logCtxt); + off_t pos = qemuDomainLogContextGetPosition(logCtxt); if (logfd >= 0) { qemuProcessReportLogError(logfd, pos, _("Process exited prior to exec")); - VIR_FORCE_CLOSE(logfd); } goto error; } @@ -4691,7 +4687,7 @@ int qemuProcessStart(virConnectPtr conn, goto error; VIR_DEBUG("Waiting for monitor to show up"); - if (qemuProcessWaitForMonitor(driver, vm, asyncJob, priv->qemuCaps, pos) < 0) + if (qemuProcessWaitForMonitor(driver, vm, asyncJob, priv->qemuCaps, logCtxt) < 0) goto error; /* Failure to connect to agent shouldn't be fatal */ @@ -4854,7 +4850,7 @@ int qemuProcessStart(virConnectPtr conn, cleanup: virCommandFree(cmd); - VIR_FORCE_CLOSE(logfile); + qemuDomainLogContextFree(logCtxt); virObjectUnref(cfg); virObjectUnref(caps); VIR_FREE(nicindexes); @@ -4868,6 +4864,9 @@ int qemuProcessStart(virConnectPtr conn, * pretend we never started it */ if (priv->mon) qemuMonitorSetDomainLog(priv->mon, -1, -1); + /* Must close log now to allow ProcessSto to re-open it */ + qemuDomainLogContextFree(logCtxt); + logCtxt = NULL; qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED, stop_flags); goto cleanup; @@ -4919,11 +4918,11 @@ void qemuProcessStop(virQEMUDriverPtr driver, virDomainDefPtr def; virNetDevVPortProfilePtr vport = NULL; size_t i; - int logfile = -1; char *timestamp; char *tmppath = NULL; char ebuf[1024]; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); + qemuDomainLogContextPtr logCtxt = NULL; VIR_DEBUG("Shutting down vm=%p name=%s id=%d pid=%llu flags=%x", vm, vm->def->name, vm->def->id, @@ -4952,12 +4951,9 @@ 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 ((logCtxt = qemuDomainLogContextNew(driver, vm, + QEMU_DOMAIN_LOG_CONTEXT_MODE_STOP))) { + int logfile = qemuDomainLogContextGetWriteFD(logCtxt); if ((timestamp = virTimeStringNow()) != NULL) { if (safewrite(logfile, timestamp, strlen(timestamp)) < 0 || safewrite(logfile, SHUTDOWN_POSTFIX, @@ -4968,10 +4964,7 @@ void qemuProcessStop(virQEMUDriverPtr driver, VIR_FREE(timestamp); } - - if (VIR_CLOSE(logfile) < 0) - VIR_WARN("Unable to close logfile: %s", - virStrerror(errno, ebuf, sizeof(ebuf))); + qemuDomainLogContextFree(logCtxt); } /* Clear network bandwidth */ @@ -5232,6 +5225,7 @@ int qemuProcessAttach(virConnectPtr conn ATTRIBUTE_UNUSED, size_t i; char ebuf[1024]; int logfile = -1; + qemuDomainLogContextPtr logCtxt = NULL; char *timestamp; qemuDomainObjPrivatePtr priv = vm->privateData; bool running = true; @@ -5326,8 +5320,10 @@ int qemuProcessAttach(virConnectPtr conn ATTRIBUTE_UNUSED, goto error; VIR_DEBUG("Creating domain log file"); - if ((logfile = qemuDomainCreateLog(driver, vm, false)) < 0) + if (!(logCtxt = qemuDomainLogContextNew(driver, vm, + QEMU_DOMAIN_LOG_CONTEXT_MODE_ATTACH))) goto error; + logfile = qemuDomainLogContextGetWriteFD(logCtxt); VIR_DEBUG("Determining emulator version"); virObjectUnref(priv->qemuCaps); @@ -5356,22 +5352,20 @@ int qemuProcessAttach(virConnectPtr conn ATTRIBUTE_UNUSED, goto error; } - if ((timestamp = virTimeStringNow()) == NULL) { + if ((timestamp = virTimeStringNow()) == NULL) goto error; - } else { - if (safewrite(logfile, timestamp, strlen(timestamp)) < 0 || - safewrite(logfile, ATTACH_POSTFIX, strlen(ATTACH_POSTFIX)) < 0) { - VIR_WARN("Unable to write timestamp to logfile: %s", - virStrerror(errno, ebuf, sizeof(ebuf))); - } - VIR_FREE(timestamp); + if (safewrite(logfile, timestamp, strlen(timestamp)) < 0 || + safewrite(logfile, ATTACH_POSTFIX, strlen(ATTACH_POSTFIX)) < 0) { + VIR_WARN("Unable to write timestamp to logfile: %s", + virStrerror(errno, ebuf, sizeof(ebuf))); } + VIR_FREE(timestamp); qemuDomainObjTaint(driver, vm, VIR_DOMAIN_TAINT_EXTERNAL_LAUNCH, logfile); VIR_DEBUG("Waiting for monitor to show up"); - if (qemuProcessWaitForMonitor(driver, vm, QEMU_ASYNC_JOB_NONE, priv->qemuCaps, -1) < 0) + if (qemuProcessWaitForMonitor(driver, vm, QEMU_ASYNC_JOB_NONE, priv->qemuCaps, NULL) < 0) goto error; /* Failure to connect to agent shouldn't be fatal */ @@ -5449,7 +5443,7 @@ int qemuProcessAttach(virConnectPtr conn ATTRIBUTE_UNUSED, goto error; } - VIR_FORCE_CLOSE(logfile); + qemuDomainLogContextFree(logCtxt); VIR_FREE(seclabel); VIR_FREE(sec_managers); virObjectUnref(cfg); @@ -5466,7 +5460,7 @@ int qemuProcessAttach(virConnectPtr conn ATTRIBUTE_UNUSED, if (active && virAtomicIntDecAndTest(&driver->nactive) && driver->inhibitCallback) driver->inhibitCallback(false, driver->inhibitOpaque); - VIR_FORCE_CLOSE(logfile); + qemuDomainLogContextFree(logCtxt); VIR_FREE(seclabel); VIR_FREE(sec_managers); if (seclabelgen) -- 2.5.0

On 11/12/2015 12:19 PM, Daniel P. Berrange wrote:
Convert the places which create/open log files to use the new qemuDomainLogContextPtr object instead.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu_domain.c | 100 +++++------------------------------------------- src/qemu/qemu_domain.h | 2 - src/qemu/qemu_process.c | 78 +++++++++++++++++-------------------- 3 files changed, 46 insertions(+), 134 deletions(-)
[...]
--- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c
[...]
@@ -4854,7 +4850,7 @@ int qemuProcessStart(virConnectPtr conn,
cleanup: virCommandFree(cmd); - VIR_FORCE_CLOSE(logfile); + qemuDomainLogContextFree(logCtxt); virObjectUnref(cfg); virObjectUnref(caps); VIR_FREE(nicindexes); @@ -4868,6 +4864,9 @@ int qemuProcessStart(virConnectPtr conn, * pretend we never started it */ if (priv->mon) qemuMonitorSetDomainLog(priv->mon, -1, -1); + /* Must close log now to allow ProcessSto to re-open it */
ProcessStop (almost not worth mentioning ;-))
+ qemuDomainLogContextFree(logCtxt); + logCtxt = NULL; qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED, stop_flags); goto cleanup;
[...] ACK John

The qemuDomainTaint APIs currently expect to be passed a log file descriptor. Change them to instead use a qemuDomainLogContextPtr to hide the implementation details. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu_domain.c | 96 ++++++++++++++++++------------------------------- src/qemu/qemu_domain.h | 15 +++----- src/qemu/qemu_driver.c | 10 +++--- src/qemu/qemu_process.c | 4 +-- 4 files changed, 47 insertions(+), 78 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index f3bb8d4..75f78fe 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -2107,9 +2107,10 @@ qemuDomainDefFormatLive(virQEMUDriverPtr driver, void qemuDomainObjTaint(virQEMUDriverPtr driver, virDomainObjPtr obj, virDomainTaintFlags taint, - int logFD) + qemuDomainLogContextPtr logCtxt) { virErrorPtr orig_err = NULL; + bool closeLog = false; if (virDomainObjTaint(obj, taint)) { char uuidstr[VIR_UUID_STRING_BUFLEN]; @@ -2125,11 +2126,23 @@ void qemuDomainObjTaint(virQEMUDriverPtr driver, * preserve original error, and clear any error that * is raised */ orig_err = virSaveLastError(); - if (qemuDomainAppendLog(driver, obj, logFD, - "Domain id=%d is tainted: %s\n", - obj->def->id, - virDomainTaintTypeToString(taint)) < 0) + if (logCtxt == NULL) { + logCtxt = qemuDomainLogContextNew(driver, obj, + QEMU_DOMAIN_LOG_CONTEXT_MODE_ATTACH); + if (!logCtxt) { + VIR_WARN("Unable to open domainlog"); + return; + } + closeLog = true; + } + + if (qemuDomainLogContextWrite(logCtxt, + "Domain id=%d is tainted: %s\n", + obj->def->id, + virDomainTaintTypeToString(taint)) < 0) virResetLastError(); + if (closeLog) + qemuDomainLogContextFree(logCtxt); if (orig_err) { virSetError(orig_err); virFreeError(orig_err); @@ -2140,7 +2153,7 @@ void qemuDomainObjTaint(virQEMUDriverPtr driver, void qemuDomainObjCheckTaint(virQEMUDriverPtr driver, virDomainObjPtr obj, - int logFD) + qemuDomainLogContextPtr logCtxt) { size_t i; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); @@ -2150,32 +2163,32 @@ void qemuDomainObjCheckTaint(virQEMUDriverPtr driver, (!cfg->clearEmulatorCapabilities || cfg->user == 0 || cfg->group == 0)) - qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_HIGH_PRIVILEGES, logFD); + qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_HIGH_PRIVILEGES, logCtxt); if (priv->hookRun) - qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_HOOK, logFD); + qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_HOOK, logCtxt); if (obj->def->namespaceData) { qemuDomainCmdlineDefPtr qemucmd = obj->def->namespaceData; if (qemucmd->num_args || qemucmd->num_env) - qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_CUSTOM_ARGV, logFD); + qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_CUSTOM_ARGV, logCtxt); } if (obj->def->cpu && obj->def->cpu->mode == VIR_CPU_MODE_HOST_PASSTHROUGH) - qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_HOST_CPU, logFD); + qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_HOST_CPU, logCtxt); for (i = 0; i < obj->def->ndisks; i++) - qemuDomainObjCheckDiskTaint(driver, obj, obj->def->disks[i], logFD); + qemuDomainObjCheckDiskTaint(driver, obj, obj->def->disks[i], logCtxt); for (i = 0; i < obj->def->nhostdevs; i++) qemuDomainObjCheckHostdevTaint(driver, obj, obj->def->hostdevs[i], - logFD); + logCtxt); for (i = 0; i < obj->def->nnets; i++) - qemuDomainObjCheckNetTaint(driver, obj, obj->def->nets[i], logFD); + qemuDomainObjCheckNetTaint(driver, obj, obj->def->nets[i], logCtxt); if (obj->def->os.dtb) - qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_CUSTOM_DTB, logFD); + qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_CUSTOM_DTB, logCtxt); virObjectUnref(cfg); } @@ -2184,24 +2197,24 @@ void qemuDomainObjCheckTaint(virQEMUDriverPtr driver, void qemuDomainObjCheckDiskTaint(virQEMUDriverPtr driver, virDomainObjPtr obj, virDomainDiskDefPtr disk, - int logFD) + qemuDomainLogContextPtr logCtxt) { virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); int format = virDomainDiskGetFormat(disk); if ((!format || format == VIR_STORAGE_FILE_AUTO) && cfg->allowDiskFormatProbing) - qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_DISK_PROBING, logFD); + qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_DISK_PROBING, logCtxt); if (disk->rawio == VIR_TRISTATE_BOOL_YES) qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_HIGH_PRIVILEGES, - logFD); + logCtxt); if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM && virStorageSourceGetActualType(disk->src) == VIR_STORAGE_TYPE_BLOCK && disk->src->path) qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_CDROM_PASSTHROUGH, - logFD); + logCtxt); virObjectUnref(cfg); } @@ -2210,21 +2223,21 @@ void qemuDomainObjCheckDiskTaint(virQEMUDriverPtr driver, void qemuDomainObjCheckHostdevTaint(virQEMUDriverPtr driver, virDomainObjPtr obj, virDomainHostdevDefPtr hostdev, - int logFD) + qemuDomainLogContextPtr logCtxt) { virDomainHostdevSubsysSCSIPtr scsisrc = &hostdev->source.subsys.u.scsi; if (hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI && scsisrc->rawio == VIR_TRISTATE_BOOL_YES) qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_HIGH_PRIVILEGES, - logFD); + logCtxt); } void qemuDomainObjCheckNetTaint(virQEMUDriverPtr driver, virDomainObjPtr obj, virDomainNetDefPtr net, - int logFD) + qemuDomainLogContextPtr logCtxt) { /* script is only useful for NET_TYPE_ETHERNET (qemu) and * NET_TYPE_BRIDGE (xen), but could be (incorrectly) specified for @@ -2232,7 +2245,7 @@ void qemuDomainObjCheckNetTaint(virQEMUDriverPtr driver, * the soup, so it should taint the domain. */ if (net->script != NULL) - qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_SHELL_SCRIPTS, logFD); + qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_SHELL_SCRIPTS, logCtxt); } @@ -2406,45 +2419,6 @@ void qemuDomainLogContextFree(qemuDomainLogContextPtr ctxt) } -int qemuDomainAppendLog(virQEMUDriverPtr driver, - virDomainObjPtr obj, - int logFD, - const char *fmt, ...) -{ - va_list argptr; - char *message = NULL; - int ret = -1; - qemuDomainLogContextPtr logCtxt = NULL; - - va_start(argptr, fmt); - - if (logFD == -1) { - logCtxt = qemuDomainLogContextNew(driver, obj, - QEMU_DOMAIN_LOG_CONTEXT_MODE_ATTACH); - if (!logCtxt) - goto cleanup; - logFD = qemuDomainLogContextGetWriteFD(logCtxt); - } - - if (virVasprintf(&message, fmt, argptr) < 0) - goto cleanup; - if (safewrite(logFD, message, strlen(message)) < 0) { - virReportSystemError(errno, _("Unable to write to domain logfile %s"), - obj->def->name); - goto cleanup; - } - - ret = 0; - - cleanup: - va_end(argptr); - - qemuDomainLogContextFree(logCtxt); - - VIR_FREE(message); - return ret; -} - /* Locate an appropriate 'qemu-img' binary. */ const char * qemuFindQemuImgBinary(virQEMUDriverPtr driver) diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 51820d8..bc84e99 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -333,23 +333,23 @@ char *qemuDomainDefFormatLive(virQEMUDriverPtr driver, void qemuDomainObjTaint(virQEMUDriverPtr driver, virDomainObjPtr obj, virDomainTaintFlags taint, - int logFD); + qemuDomainLogContextPtr logCtxt); void qemuDomainObjCheckTaint(virQEMUDriverPtr driver, virDomainObjPtr obj, - int logFD); + qemuDomainLogContextPtr logCtxt); void qemuDomainObjCheckDiskTaint(virQEMUDriverPtr driver, virDomainObjPtr obj, virDomainDiskDefPtr disk, - int logFD); + qemuDomainLogContextPtr logCtxt); void qemuDomainObjCheckHostdevTaint(virQEMUDriverPtr driver, virDomainObjPtr obj, virDomainHostdevDefPtr disk, - int logFD); + qemuDomainLogContextPtr logCtxt); void qemuDomainObjCheckNetTaint(virQEMUDriverPtr driver, virDomainObjPtr obj, virDomainNetDefPtr net, - int logFD); + qemuDomainLogContextPtr logCtxt); typedef enum { QEMU_DOMAIN_LOG_CONTEXT_MODE_START, @@ -371,11 +371,6 @@ off_t qemuDomainLogContextGetPosition(qemuDomainLogContextPtr ctxt); void qemuDomainLogContextRef(qemuDomainLogContextPtr ctxt); void qemuDomainLogContextFree(qemuDomainLogContextPtr ctxt); -int qemuDomainAppendLog(virQEMUDriverPtr driver, - virDomainObjPtr vm, - int logFD, - const char *fmt, ...) ATTRIBUTE_FMT_PRINTF(4, 5); - const char *qemuFindQemuImgBinary(virQEMUDriverPtr driver); int qemuDomainSnapshotWriteMetadata(virDomainObjPtr vm, diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index d537523..bcafd22 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -7701,7 +7701,7 @@ qemuDomainAttachDeviceLive(virDomainObjPtr vm, switch ((virDomainDeviceType) dev->type) { case VIR_DOMAIN_DEVICE_DISK: - qemuDomainObjCheckDiskTaint(driver, vm, dev->data.disk, -1); + qemuDomainObjCheckDiskTaint(driver, vm, dev->data.disk, NULL); ret = qemuDomainAttachDeviceDiskLive(dom->conn, driver, vm, dev); if (!ret) { alias = dev->data.disk->info.alias; @@ -7725,7 +7725,7 @@ qemuDomainAttachDeviceLive(virDomainObjPtr vm, break; case VIR_DOMAIN_DEVICE_NET: - qemuDomainObjCheckNetTaint(driver, vm, dev->data.net, -1); + qemuDomainObjCheckNetTaint(driver, vm, dev->data.net, NULL); ret = qemuDomainAttachNetDevice(dom->conn, driver, vm, dev->data.net); if (!ret) { @@ -7735,7 +7735,7 @@ qemuDomainAttachDeviceLive(virDomainObjPtr vm, break; case VIR_DOMAIN_DEVICE_HOSTDEV: - qemuDomainObjCheckHostdevTaint(driver, vm, dev->data.hostdev, -1); + qemuDomainObjCheckHostdevTaint(driver, vm, dev->data.hostdev, NULL); ret = qemuDomainAttachHostDevice(dom->conn, driver, vm, dev->data.hostdev); if (!ret) { @@ -7983,7 +7983,7 @@ qemuDomainUpdateDeviceLive(virConnectPtr conn, switch ((virDomainDeviceType) dev->type) { case VIR_DOMAIN_DEVICE_DISK: - qemuDomainObjCheckDiskTaint(driver, vm, dev->data.disk, -1); + qemuDomainObjCheckDiskTaint(driver, vm, dev->data.disk, NULL); ret = qemuDomainChangeDiskLive(conn, vm, dev, driver, force); break; case VIR_DOMAIN_DEVICE_GRAPHICS: @@ -15815,7 +15815,7 @@ static int qemuDomainQemuMonitorCommand(virDomainPtr domain, const char *cmd, priv = vm->privateData; - qemuDomainObjTaint(driver, vm, VIR_DOMAIN_TAINT_CUSTOM_MONITOR, -1); + qemuDomainObjTaint(driver, vm, VIR_DOMAIN_TAINT_CUSTOM_MONITOR, NULL); hmp = !!(flags & VIR_DOMAIN_QEMU_MONITOR_COMMAND_HMP); diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index e433548..85ad3a5 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -4520,7 +4520,7 @@ int qemuProcessStart(virConnectPtr conn, qemuLogOperation(vm, "starting up", logfile, cmd); - qemuDomainObjCheckTaint(driver, vm, logfile); + qemuDomainObjCheckTaint(driver, vm, logCtxt); qemuDomainLogContextMarkPosition(logCtxt); @@ -5362,7 +5362,7 @@ int qemuProcessAttach(virConnectPtr conn ATTRIBUTE_UNUSED, } VIR_FREE(timestamp); - qemuDomainObjTaint(driver, vm, VIR_DOMAIN_TAINT_EXTERNAL_LAUNCH, logfile); + qemuDomainObjTaint(driver, vm, VIR_DOMAIN_TAINT_EXTERNAL_LAUNCH, logCtxt); VIR_DEBUG("Waiting for monitor to show up"); if (qemuProcessWaitForMonitor(driver, vm, QEMU_ASYNC_JOB_NONE, priv->qemuCaps, NULL) < 0) -- 2.5.0

On 11/12/2015 12:19 PM, Daniel P. Berrange wrote:
The qemuDomainTaint APIs currently expect to be passed a log file descriptor. Change them to instead use a qemuDomainLogContextPtr to hide the implementation details.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu_domain.c | 96 ++++++++++++++++++------------------------------- src/qemu/qemu_domain.h | 15 +++----- src/qemu/qemu_driver.c | 10 +++--- src/qemu/qemu_process.c | 4 +-- 4 files changed, 47 insertions(+), 78 deletions(-)
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index f3bb8d4..75f78fe 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -2107,9 +2107,10 @@ qemuDomainDefFormatLive(virQEMUDriverPtr driver, void qemuDomainObjTaint(virQEMUDriverPtr driver, virDomainObjPtr obj, virDomainTaintFlags taint, - int logFD) + qemuDomainLogContextPtr logCtxt) { virErrorPtr orig_err = NULL; + bool closeLog = false;
if (virDomainObjTaint(obj, taint)) { char uuidstr[VIR_UUID_STRING_BUFLEN]; @@ -2125,11 +2126,23 @@ void qemuDomainObjTaint(virQEMUDriverPtr driver, * preserve original error, and clear any error that * is raised */ orig_err = virSaveLastError(); - if (qemuDomainAppendLog(driver, obj, logFD, - "Domain id=%d is tainted: %s\n", - obj->def->id, - virDomainTaintTypeToString(taint)) < 0) + if (logCtxt == NULL) { + logCtxt = qemuDomainLogContextNew(driver, obj, + QEMU_DOMAIN_LOG_CONTEXT_MODE_ATTACH); + if (!logCtxt) { + VIR_WARN("Unable to open domainlog");
Coverity found - leaking orig_err
+ return; + } + closeLog = true; + } + + if (qemuDomainLogContextWrite(logCtxt, + "Domain id=%d is tainted: %s\n", + obj->def->id, + virDomainTaintTypeToString(taint)) < 0) virResetLastError(); + if (closeLog) + qemuDomainLogContextFree(logCtxt); if (orig_err) { virSetError(orig_err); virFreeError(orig_err); @@ -2140,7 +2153,7 @@ void qemuDomainObjTaint(virQEMUDriverPtr driver,
[...] ACK with the adjustment John

Instead of writing directly to a log file descriptor, change qemuLogOperation to use qemuDomainLogContextWrite(). Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu_process.c | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 85ad3a5..f729065 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -4064,40 +4064,33 @@ qemuPrepareNVRAM(virQEMUDriverConfigPtr cfg, static void qemuLogOperation(virDomainObjPtr vm, const char *msg, - int logfd, - virCommandPtr cmd) + virCommandPtr cmd, + qemuDomainLogContextPtr logCtxt) { char *timestamp; - char *logline; qemuDomainObjPrivatePtr priv = vm->privateData; int qemuVersion = virQEMUCapsGetVersion(priv->qemuCaps); const char *package = virQEMUCapsGetPackage(priv->qemuCaps); - char ebuf[1024]; if ((timestamp = virTimeStringNow()) == NULL) - goto error; - - if (virAsprintf(&logline, "%s: %s %s, qemu version: %d.%d.%d%s\n", - timestamp, msg, VIR_LOG_VERSION_STRING, - (qemuVersion / 1000000) % 1000, (qemuVersion / 1000) % 1000, qemuVersion % 1000, - package ? package : "") < 0) - goto error; + goto cleanup; - if (safewrite(logfd, logline, strlen(logline)) < 0) - goto error; + if (qemuDomainLogContextWrite(logCtxt, "%s: %s %s, qemu version: %d.%d.%d%s\n", + timestamp, msg, VIR_LOG_VERSION_STRING, + (qemuVersion / 1000000) % 1000, + (qemuVersion / 1000) % 1000, + qemuVersion % 1000, + package ? package : "") < 0) + goto cleanup; - if (cmd) - virCommandWriteArgLog(cmd, logfd); + if (cmd) { + char *args = virCommandToString(cmd); + qemuDomainLogContextWrite(logCtxt, "%s", args); + VIR_FREE(args); + } cleanup: VIR_FREE(timestamp); - VIR_FREE(logline); - return; - - error: - VIR_WARN("Unable to write banner to logfile: %s", - virStrerror(errno, ebuf, sizeof(ebuf))); - goto cleanup; } int qemuProcessStart(virConnectPtr conn, @@ -4518,7 +4511,7 @@ int qemuProcessStart(virConnectPtr conn, goto error; } - qemuLogOperation(vm, "starting up", logfile, cmd); + qemuLogOperation(vm, "starting up", cmd, logCtxt); qemuDomainObjCheckTaint(driver, vm, logCtxt); -- 2.5.0

On 11/12/2015 12:19 PM, Daniel P. Berrange wrote:
Instead of writing directly to a log file descriptor, change qemuLogOperation to use qemuDomainLogContextWrite().
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu_process.c | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-)
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 85ad3a5..f729065 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -4064,40 +4064,33 @@ qemuPrepareNVRAM(virQEMUDriverConfigPtr cfg, static void qemuLogOperation(virDomainObjPtr vm, const char *msg, - int logfd, - virCommandPtr cmd) + virCommandPtr cmd, + qemuDomainLogContextPtr logCtxt) { char *timestamp; - char *logline; qemuDomainObjPrivatePtr priv = vm->privateData; int qemuVersion = virQEMUCapsGetVersion(priv->qemuCaps); const char *package = virQEMUCapsGetPackage(priv->qemuCaps); - char ebuf[1024];
if ((timestamp = virTimeStringNow()) == NULL) - goto error; - - if (virAsprintf(&logline, "%s: %s %s, qemu version: %d.%d.%d%s\n", - timestamp, msg, VIR_LOG_VERSION_STRING, - (qemuVersion / 1000000) % 1000, (qemuVersion / 1000) % 1000, qemuVersion % 1000, - package ? package : "") < 0) - goto error; + goto cleanup;
- if (safewrite(logfd, logline, strlen(logline)) < 0) - goto error; + if (qemuDomainLogContextWrite(logCtxt, "%s: %s %s, qemu version: %d.%d.%d%s\n", + timestamp, msg, VIR_LOG_VERSION_STRING, + (qemuVersion / 1000000) % 1000, + (qemuVersion / 1000) % 1000, + qemuVersion % 1000, + package ? package : "") < 0) + goto cleanup;
- if (cmd) - virCommandWriteArgLog(cmd, logfd); + if (cmd) { + char *args = virCommandToString(cmd); + qemuDomainLogContextWrite(logCtxt, "%s", args);
Should this be "%s\n"? Seems virCommandWriteArgLog will print one at the end and I see callers to virCommandToString will add the '\n' character... ACK with the "expected result"... John
+ VIR_FREE(args); + }
cleanup: VIR_FREE(timestamp); - VIR_FREE(logline); - return; - - error: - VIR_WARN("Unable to write banner to logfile: %s", - virStrerror(errno, ebuf, sizeof(ebuf))); - goto cleanup; }
int qemuProcessStart(virConnectPtr conn, @@ -4518,7 +4511,7 @@ int qemuProcessStart(virConnectPtr conn, goto error; }
- qemuLogOperation(vm, "starting up", logfile, cmd); + qemuLogOperation(vm, "starting up", cmd, logCtxt);
qemuDomainObjCheckTaint(driver, vm, logCtxt);

When the qemuProcessAttach/Stop methods write a marker into the log file, they can use qemuDomainLogContextWrite to write a formatted message. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu_process.c | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index f729065..132b3eb 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -77,9 +77,6 @@ VIR_LOG_INIT("qemu.qemu_process"); -#define ATTACH_POSTFIX ": attaching\n" -#define SHUTDOWN_POSTFIX ": shutting down\n" - /** * qemuProcessRemoveDomainStatus * @@ -4913,7 +4910,6 @@ void qemuProcessStop(virQEMUDriverPtr driver, size_t i; char *timestamp; char *tmppath = NULL; - char ebuf[1024]; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); qemuDomainLogContextPtr logCtxt = NULL; @@ -4946,15 +4942,8 @@ void qemuProcessStop(virQEMUDriverPtr driver, if ((logCtxt = qemuDomainLogContextNew(driver, vm, QEMU_DOMAIN_LOG_CONTEXT_MODE_STOP))) { - int logfile = qemuDomainLogContextGetWriteFD(logCtxt); 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))); - } - + qemuDomainLogContextWrite(logCtxt, "%s: stopping\n", timestamp); VIR_FREE(timestamp); } qemuDomainLogContextFree(logCtxt); @@ -5216,8 +5205,6 @@ int qemuProcessAttach(virConnectPtr conn ATTRIBUTE_UNUSED, bool monJSON) { size_t i; - char ebuf[1024]; - int logfile = -1; qemuDomainLogContextPtr logCtxt = NULL; char *timestamp; qemuDomainObjPrivatePtr priv = vm->privateData; @@ -5316,7 +5303,6 @@ int qemuProcessAttach(virConnectPtr conn ATTRIBUTE_UNUSED, if (!(logCtxt = qemuDomainLogContextNew(driver, vm, QEMU_DOMAIN_LOG_CONTEXT_MODE_ATTACH))) goto error; - logfile = qemuDomainLogContextGetWriteFD(logCtxt); VIR_DEBUG("Determining emulator version"); virObjectUnref(priv->qemuCaps); @@ -5348,11 +5334,7 @@ int qemuProcessAttach(virConnectPtr conn ATTRIBUTE_UNUSED, if ((timestamp = virTimeStringNow()) == NULL) goto error; - if (safewrite(logfile, timestamp, strlen(timestamp)) < 0 || - safewrite(logfile, ATTACH_POSTFIX, strlen(ATTACH_POSTFIX)) < 0) { - VIR_WARN("Unable to write timestamp to logfile: %s", - virStrerror(errno, ebuf, sizeof(ebuf))); - } + qemuDomainLogContextWrite(logCtxt, "%s: attaching\n", timestamp); VIR_FREE(timestamp); qemuDomainObjTaint(driver, vm, VIR_DOMAIN_TAINT_EXTERNAL_LAUNCH, logCtxt); -- 2.5.0

On 11/12/2015 12:19 PM, Daniel P. Berrange wrote:
When the qemuProcessAttach/Stop methods write a marker into the log file, they can use qemuDomainLogContextWrite to write a formatted message.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu_process.c | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-)
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index f729065..132b3eb 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -77,9 +77,6 @@
VIR_LOG_INIT("qemu.qemu_process");
-#define ATTACH_POSTFIX ": attaching\n" -#define SHUTDOWN_POSTFIX ": shutting down\n" - /** * qemuProcessRemoveDomainStatus * @@ -4913,7 +4910,6 @@ void qemuProcessStop(virQEMUDriverPtr driver, size_t i; char *timestamp; char *tmppath = NULL; - char ebuf[1024]; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); qemuDomainLogContextPtr logCtxt = NULL;
@@ -4946,15 +4942,8 @@ void qemuProcessStop(virQEMUDriverPtr driver,
if ((logCtxt = qemuDomainLogContextNew(driver, vm, QEMU_DOMAIN_LOG_CONTEXT_MODE_STOP))) { - int logfile = qemuDomainLogContextGetWriteFD(logCtxt); 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))); - } - + qemuDomainLogContextWrite(logCtxt, "%s: stopping\n", timestamp);
was #define SHUTDOWN_POSTFIX ": shutting down\n", but this changes to "stopping" which is 'different', although perhaps more technically correct w/r/t what's going on here. I have to believe there's scripts out in the wild looking for 'shutting down' that fail with 'stopping'... For some reason I seem to recall tests scripts that would essentially 'tail -f' the log file for that to ensure that whatever shutdown/stop command was sent actually worked. ACK - either way, but I tend to favor shutdown down only because it has precedence. John
VIR_FREE(timestamp); } qemuDomainLogContextFree(logCtxt); @@ -5216,8 +5205,6 @@ int qemuProcessAttach(virConnectPtr conn ATTRIBUTE_UNUSED, bool monJSON) { size_t i; - char ebuf[1024]; - int logfile = -1; qemuDomainLogContextPtr logCtxt = NULL; char *timestamp; qemuDomainObjPrivatePtr priv = vm->privateData; @@ -5316,7 +5303,6 @@ int qemuProcessAttach(virConnectPtr conn ATTRIBUTE_UNUSED, if (!(logCtxt = qemuDomainLogContextNew(driver, vm, QEMU_DOMAIN_LOG_CONTEXT_MODE_ATTACH))) goto error; - logfile = qemuDomainLogContextGetWriteFD(logCtxt);
VIR_DEBUG("Determining emulator version"); virObjectUnref(priv->qemuCaps); @@ -5348,11 +5334,7 @@ int qemuProcessAttach(virConnectPtr conn ATTRIBUTE_UNUSED, if ((timestamp = virTimeStringNow()) == NULL) goto error;
- if (safewrite(logfile, timestamp, strlen(timestamp)) < 0 || - safewrite(logfile, ATTACH_POSTFIX, strlen(ATTACH_POSTFIX)) < 0) { - VIR_WARN("Unable to write timestamp to logfile: %s", - virStrerror(errno, ebuf, sizeof(ebuf))); - } + qemuDomainLogContextWrite(logCtxt, "%s: attaching\n", timestamp); VIR_FREE(timestamp);
qemuDomainObjTaint(driver, vm, VIR_DOMAIN_TAINT_EXTERNAL_LAUNCH, logCtxt);

Currently the QEMU monitor is given an FD to the logfile. This won't work in the future with virtlogd, so it needs to use the qemuDomainLogContextPtr instead, but it shouldn't directly access that object either. So define a callback that the monitor can use for reporting errors from the log file. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu_domain.c | 12 ------ src/qemu/qemu_domain.h | 2 - src/qemu/qemu_migration.c | 2 +- src/qemu/qemu_monitor.c | 51 ++++++++++++++------------ src/qemu/qemu_monitor.h | 8 +++- src/qemu/qemu_process.c | 93 ++++++++++++++++++++++++----------------------- src/qemu/qemu_process.h | 4 -- 7 files changed, 84 insertions(+), 88 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 75f78fe..2417cb7 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -2378,24 +2378,12 @@ int qemuDomainLogContextGetWriteFD(qemuDomainLogContextPtr ctxt) } -int qemuDomainLogContextGetReadFD(qemuDomainLogContextPtr ctxt) -{ - return ctxt->readfd; -} - - void qemuDomainLogContextMarkPosition(qemuDomainLogContextPtr ctxt) { ctxt->pos = lseek(ctxt->writefd, 0, SEEK_END); } -off_t qemuDomainLogContextGetPosition(qemuDomainLogContextPtr ctxt) -{ - return ctxt->pos; -} - - void qemuDomainLogContextRef(qemuDomainLogContextPtr ctxt) { virAtomicIntInc(&ctxt->refs); diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index bc84e99..ac49377 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -365,9 +365,7 @@ int qemuDomainLogContextWrite(qemuDomainLogContextPtr ctxt, ssize_t qemuDomainLogContextRead(qemuDomainLogContextPtr ctxt, char **msg); int qemuDomainLogContextGetWriteFD(qemuDomainLogContextPtr ctxt); -int qemuDomainLogContextGetReadFD(qemuDomainLogContextPtr ctxt); void qemuDomainLogContextMarkPosition(qemuDomainLogContextPtr ctxt); -off_t qemuDomainLogContextGetPosition(qemuDomainLogContextPtr ctxt); void qemuDomainLogContextRef(qemuDomainLogContextPtr ctxt); void qemuDomainLogContextFree(qemuDomainLogContextPtr ctxt); diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 475f97d..3a1c068 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -5849,7 +5849,7 @@ qemuMigrationFinish(virQEMUDriverPtr driver, cleanup: virPortAllocatorRelease(driver->migrationPorts, port); if (priv->mon) - qemuMonitorSetDomainLog(priv->mon, -1, -1); + qemuMonitorSetDomainLog(priv->mon, NULL, NULL, NULL); VIR_FREE(priv->origname); virDomainObjEndAPI(&vm); if (mig) { diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index d3f0c09..bd5d9ca 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -94,9 +94,10 @@ struct _qemuMonitor { char *balloonpath; bool ballooninit; - /* Log file fd of the qemu process to dig for usable info */ - int logfd; - off_t logpos; + /* Log file context of the qemu process to dig for usable info */ + qemuMonitorReportDomainLogError logFunc; + void *logOpaque; + virFreeCallback logDestroy; }; /** @@ -315,7 +316,6 @@ qemuMonitorDispose(void *obj) VIR_FREE(mon->buffer); virJSONValueFree(mon->options); VIR_FREE(mon->balloonpath); - VIR_FORCE_CLOSE(mon->logfd); } @@ -706,18 +706,17 @@ qemuMonitorIO(int watch, int fd, int events, void *opaque) } if (error || eof) { - if (hangup && mon->logfd != -1) { + if (hangup && mon->logFunc != NULL) { /* Check if an error message from qemu is available and if so, use * it to overwrite the actual message. It's done only in early * startup phases or during incoming migration when the message * from qemu is certainly more interesting than a * "connection reset by peer" message. */ - qemuProcessReportLogError(mon->logfd, - mon->logpos, - _("early end of file from monitor, " - "possible problem")); - VIR_FORCE_CLOSE(mon->logfd); + mon->logFunc(mon, + _("early end of file from monitor, " + "possible problem"), + mon->logOpaque); virCopyLastError(&mon->lastError); virResetLastError(); } @@ -802,7 +801,6 @@ qemuMonitorOpenInternal(virDomainObjPtr vm, if (!(mon = virObjectLockableNew(qemuMonitorClass))) return NULL; - mon->logfd = -1; if (virCondInit(&mon->notify) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("cannot initialize monitor condition")); @@ -932,6 +930,13 @@ qemuMonitorClose(qemuMonitorPtr mon) VIR_FORCE_CLOSE(mon->fd); } + if (mon->logDestroy && mon->logOpaque) { + mon->logDestroy(mon->logOpaque); + mon->logOpaque = NULL; + mon->logDestroy = NULL; + mon->logFunc = NULL; + } + /* In case another thread is waiting for its monitor command to be * processed, we need to wake it up with appropriate error set. */ @@ -3646,21 +3651,21 @@ qemuMonitorGetDeviceAliases(qemuMonitorPtr mon, * early startup errors of qemu. * * @mon: Monitor object to set the log file reading on - * @logfd: File descriptor of the already open log file - * @pos: position to read errors from + * @func: the callback to report errors + * @opaque: data to pass to @func */ -int -qemuMonitorSetDomainLog(qemuMonitorPtr mon, int logfd, off_t pos) +void +qemuMonitorSetDomainLog(qemuMonitorPtr mon, + qemuMonitorReportDomainLogError func, + void *opaque, + virFreeCallback destroy) { - VIR_FORCE_CLOSE(mon->logfd); - if (logfd >= 0 && - (mon->logfd = dup(logfd)) < 0) { - virReportSystemError(errno, "%s", _("failed to duplicate log fd")); - return -1; - } - mon->logpos = pos; + if (mon->logDestroy && mon->logOpaque) + mon->logDestroy(mon->logOpaque); - return 0; + mon->logFunc = func; + mon->logOpaque = opaque; + mon->logDestroy = destroy; } diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index a2a9a77..29e211f 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -892,7 +892,13 @@ int qemuMonitorDetachCharDev(qemuMonitorPtr mon, int qemuMonitorGetDeviceAliases(qemuMonitorPtr mon, char ***aliases); -int qemuMonitorSetDomainLog(qemuMonitorPtr mon, int logfd, off_t pos); +typedef void (*qemuMonitorReportDomainLogError)(qemuMonitorPtr mon, + const char *msg, + void *opaque); +void qemuMonitorSetDomainLog(qemuMonitorPtr mon, + qemuMonitorReportDomainLogError func, + void *opaque, + virFreeCallback destroy); int qemuMonitorGetGuestCPU(qemuMonitorPtr mon, virArch arch, diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 132b3eb..4a2e2c1 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -1544,9 +1544,22 @@ static qemuMonitorCallbacks monitorCallbacks = { .domainMigrationStatus = qemuProcessHandleMigrationStatus, }; +static void +qemuProcessMonitorReportLogError(qemuMonitorPtr mon, + const char *msg, + void *opaque); + + +static void +qemuProcessMonitorLogFree(void *opaque) +{ + qemuDomainLogContextPtr logCtxt = opaque; + qemuDomainLogContextFree(logCtxt); +} + static int qemuConnectMonitor(virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob, - int logfd, off_t pos) + qemuDomainLogContextPtr logCtxt) { qemuDomainObjPrivatePtr priv = vm->privateData; int ret = -1; @@ -1572,8 +1585,13 @@ qemuConnectMonitor(virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob, &monitorCallbacks, driver); - if (mon && logfd != -1 && pos != -1) - ignore_value(qemuMonitorSetDomainLog(mon, logfd, pos)); + if (mon && logCtxt) { + qemuDomainLogContextRef(logCtxt); + qemuMonitorSetDomainLog(mon, + qemuProcessMonitorReportLogError, + logCtxt, + qemuProcessMonitorLogFree); + } virObjectLock(vm); virObjectUnref(vm); @@ -1626,36 +1644,22 @@ qemuConnectMonitor(virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob, /** * qemuProcessReadLog: Read log file of a qemu VM - * @fd: File descriptor of the log file + * @logCtxt: the domain log context * @msg: pointer to buffer to store the read messages in - * @off: Offset to start reading from * * Reads log of a qemu VM. Skips messages not produced by qemu or irrelevant * messages. Returns returns 0 on success or -1 on error */ static int -qemuProcessReadLog(int fd, off_t offset, char **msg) +qemuProcessReadLog(qemuDomainLogContextPtr logCtxt, char **msg) { char *buf; - size_t buflen = 1024 * 128; ssize_t got; char *eol; char *filter_next; - /* Best effort jump to start of messages */ - ignore_value(lseek(fd, offset, SEEK_SET)); - - if (VIR_ALLOC_N(buf, buflen) < 0) - return -1; - - got = saferead(fd, buf, buflen - 1); - if (got < 0) { - virReportSystemError(errno, "%s", - _("Unable to read from log file")); + if ((got = qemuDomainLogContextRead(logCtxt, &buf)) < 0) return -1; - } - - buf[got] = '\0'; /* Filter out debug messages from intermediate libvirt process */ filter_next = buf; @@ -1672,24 +1676,24 @@ qemuProcessReadLog(int fd, off_t offset, char **msg) } } - if (buf[got - 1] == '\n') { + if (got > 0 && + buf[got - 1] == '\n') { buf[got - 1] = '\0'; got--; } - VIR_SHRINK_N(buf, buflen, buflen - got - 1); + ignore_value(VIR_REALLOC_N_QUIET(buf, got + 1)); *msg = buf; return 0; } -int -qemuProcessReportLogError(int logfd, - off_t offset, +static int +qemuProcessReportLogError(qemuDomainLogContextPtr logCtxt, const char *msgprefix) { char *logmsg = NULL; - if (qemuProcessReadLog(logfd, offset, &logmsg) < 0) + if (qemuProcessReadLog(logCtxt, &logmsg) < 0) return -1; virResetLastError(); @@ -1700,6 +1704,16 @@ qemuProcessReportLogError(int logfd, } +static void +qemuProcessMonitorReportLogError(qemuMonitorPtr mon ATTRIBUTE_UNUSED, + const char *msg, + void *opaque) +{ + qemuDomainLogContextPtr logCtxt = opaque; + qemuProcessReportLogError(logCtxt, msg); +} + + static int qemuProcessLookupPTYs(virDomainDefPtr def, virQEMUCapsPtr qemuCaps, @@ -1909,16 +1923,9 @@ qemuProcessWaitForMonitor(virQEMUDriverPtr driver, int ret = -1; virHashTablePtr info = NULL; qemuDomainObjPrivatePtr priv; - int logfd = -1; - off_t pos = -1; - - if (logCtxt) { - logfd = qemuDomainLogContextGetReadFD(logCtxt); - pos = qemuDomainLogContextGetPosition(logCtxt); - } VIR_DEBUG("Connect monitor to %p '%s'", vm, vm->def->name); - if (qemuConnectMonitor(driver, vm, asyncJob, logfd, pos) < 0) + if (qemuConnectMonitor(driver, vm, asyncJob, logCtxt) < 0) goto cleanup; /* Try to get the pty path mappings again via the monitor. This is much more @@ -1946,8 +1953,8 @@ qemuProcessWaitForMonitor(virQEMUDriverPtr driver, cleanup: virHashFree(info); - if (pos != (off_t)-1 && kill(vm->pid, 0) == -1 && errno == ESRCH) { - qemuProcessReportLogError(logfd, pos, + if (logCtxt && kill(vm->pid, 0) == -1 && errno == ESRCH) { + qemuProcessReportLogError(logCtxt, _("process exited while connecting to monitor")); ret = -1; } @@ -3498,7 +3505,7 @@ qemuProcessReconnect(void *opaque) VIR_DEBUG("Reconnect monitor to %p '%s'", obj, obj->def->name); /* XXX check PID liveliness & EXE path */ - if (qemuConnectMonitor(driver, obj, QEMU_ASYNC_JOB_NONE, -1, -1) < 0) + if (qemuConnectMonitor(driver, obj, QEMU_ASYNC_JOB_NONE, NULL) < 0) goto error; /* Failure to connect to agent shouldn't be fatal */ @@ -4606,12 +4613,8 @@ int qemuProcessStart(virConnectPtr conn, VIR_DEBUG("Waiting for handshake from child"); if (virCommandHandshakeWait(cmd) < 0) { /* Read errors from child that occurred between fork and exec. */ - int logfd = qemuDomainLogContextGetReadFD(logCtxt); - off_t pos = qemuDomainLogContextGetPosition(logCtxt); - if (logfd >= 0) { - qemuProcessReportLogError(logfd, pos, - _("Process exited prior to exec")); - } + qemuProcessReportLogError(logCtxt, + _("Process exited prior to exec")); goto error; } @@ -4834,7 +4837,7 @@ int qemuProcessStart(virConnectPtr conn, /* Keep watching qemu log for errors during incoming migration, otherwise * unset reporting errors from qemu log. */ if (!migrateFrom) - qemuMonitorSetDomainLog(priv->mon, -1, -1); + qemuMonitorSetDomainLog(priv->mon, NULL, NULL, NULL); ret = 0; @@ -4853,7 +4856,7 @@ int qemuProcessStart(virConnectPtr conn, * if we failed to initialize the now running VM. kill it off and * pretend we never started it */ if (priv->mon) - qemuMonitorSetDomainLog(priv->mon, -1, -1); + qemuMonitorSetDomainLog(priv->mon, NULL, NULL, NULL); /* Must close log now to allow ProcessSto to re-open it */ qemuDomainLogContextFree(logCtxt); logCtxt = NULL; diff --git a/src/qemu/qemu_process.h b/src/qemu/qemu_process.h index 2df5c4d..640c498 100644 --- a/src/qemu/qemu_process.h +++ b/src/qemu/qemu_process.h @@ -100,10 +100,6 @@ int qemuProcessAutoDestroyRemove(virQEMUDriverPtr driver, bool qemuProcessAutoDestroyActive(virQEMUDriverPtr driver, virDomainObjPtr vm); -int qemuProcessReportLogError(int fd, - off_t offset, - const char *msgprefix); - int qemuProcessSetSchedParams(int id, pid_t pid, size_t nsp, virDomainThreadSchedParamPtr sp); -- 2.5.0

On 11/12/2015 12:19 PM, Daniel P. Berrange wrote:
Currently the QEMU monitor is given an FD to the logfile. This won't work in the future with virtlogd, so it needs to use the qemuDomainLogContextPtr instead, but it shouldn't directly access that object either. So define a callback that the monitor can use for reporting errors from the log file.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu_domain.c | 12 ------ src/qemu/qemu_domain.h | 2 - src/qemu/qemu_migration.c | 2 +- src/qemu/qemu_monitor.c | 51 ++++++++++++++------------ src/qemu/qemu_monitor.h | 8 +++- src/qemu/qemu_process.c | 93 ++++++++++++++++++++++++----------------------- src/qemu/qemu_process.h | 4 -- 7 files changed, 84 insertions(+), 88 deletions(-)
[...]
index d3f0c09..bd5d9ca 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -94,9 +94,10 @@ struct _qemuMonitor { char *balloonpath; bool ballooninit;
- /* Log file fd of the qemu process to dig for usable info */ - int logfd; - off_t logpos; + /* Log file context of the qemu process to dig for usable info */ + qemuMonitorReportDomainLogError logFunc; + void *logOpaque; + virFreeCallback logDestroy; };
/** @@ -315,7 +316,6 @@ qemuMonitorDispose(void *obj) VIR_FREE(mon->buffer); virJSONValueFree(mon->options); VIR_FREE(mon->balloonpath); - VIR_FORCE_CLOSE(mon->logfd); }
@@ -706,18 +706,17 @@ qemuMonitorIO(int watch, int fd, int events, void *opaque) }
if (error || eof) { - if (hangup && mon->logfd != -1) { + if (hangup && mon->logFunc != NULL) { /* Check if an error message from qemu is available and if so, use * it to overwrite the actual message. It's done only in early * startup phases or during incoming migration when the message * from qemu is certainly more interesting than a * "connection reset by peer" message. */ - qemuProcessReportLogError(mon->logfd, - mon->logpos, - _("early end of file from monitor, " - "possible problem")); - VIR_FORCE_CLOSE(mon->logfd); + mon->logFunc(mon, + _("early end of file from monitor, " + "possible problem"), + mon->logOpaque);
Mostly a consistency thing and not that it should happen since qemuConnectMonitor checks for logCtxt before calling qemuMonitorSetDomainLog; however, qemuMonitorClose and qemuMonitorSetDomainLog check for mon->logOpaque before calling logDestroy. Likewise, if logFunc is called with NULL (logOpaque) life won't be good. I don't think there's anything wrong with the current code - just caused me to double check things.
virCopyLastError(&mon->lastError); virResetLastError(); } @@ -802,7 +801,6 @@ qemuMonitorOpenInternal(virDomainObjPtr vm, if (!(mon = virObjectLockableNew(qemuMonitorClass))) return NULL;
- mon->logfd = -1; if (virCondInit(&mon->notify) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("cannot initialize monitor condition")); @@ -932,6 +930,13 @@ qemuMonitorClose(qemuMonitorPtr mon) VIR_FORCE_CLOSE(mon->fd); }
+ if (mon->logDestroy && mon->logOpaque) { + mon->logDestroy(mon->logOpaque); + mon->logOpaque = NULL; + mon->logDestroy = NULL; + mon->logFunc = NULL; + } + /* In case another thread is waiting for its monitor command to be * processed, we need to wake it up with appropriate error set. */ @@ -3646,21 +3651,21 @@ qemuMonitorGetDeviceAliases(qemuMonitorPtr mon, * early startup errors of qemu. * * @mon: Monitor object to set the log file reading on - * @logfd: File descriptor of the already open log file - * @pos: position to read errors from + * @func: the callback to report errors + * @opaque: data to pass to @func
Missing @destroy
*/ -int -qemuMonitorSetDomainLog(qemuMonitorPtr mon, int logfd, off_t pos) +void +qemuMonitorSetDomainLog(qemuMonitorPtr mon, + qemuMonitorReportDomainLogError func, + void *opaque, + virFreeCallback destroy)
[...]
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 132b3eb..4a2e2c1 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -1544,9 +1544,22 @@ static qemuMonitorCallbacks monitorCallbacks = { .domainMigrationStatus = qemuProcessHandleMigrationStatus, };
+static void +qemuProcessMonitorReportLogError(qemuMonitorPtr mon, + const char *msg, + void *opaque); + + +static void +qemuProcessMonitorLogFree(void *opaque) +{ + qemuDomainLogContextPtr logCtxt = opaque;
This could check opaque != NULL and avoid the other checks for logDestroy && logOpaque - your call.
+ qemuDomainLogContextFree(logCtxt); +} + static int qemuConnectMonitor(virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob, - int logfd, off_t pos) + qemuDomainLogContextPtr logCtxt) { qemuDomainObjPrivatePtr priv = vm->privateData; int ret = -1; @@ -1572,8 +1585,13 @@ qemuConnectMonitor(virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob, &monitorCallbacks, driver);
- if (mon && logfd != -1 && pos != -1) - ignore_value(qemuMonitorSetDomainLog(mon, logfd, pos)); + if (mon && logCtxt) { + qemuDomainLogContextRef(logCtxt); + qemuMonitorSetDomainLog(mon, + qemuProcessMonitorReportLogError, + logCtxt, + qemuProcessMonitorLogFree); + }
virObjectLock(vm); virObjectUnref(vm); @@ -1626,36 +1644,22 @@ qemuConnectMonitor(virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob,
/** * qemuProcessReadLog: Read log file of a qemu VM - * @fd: File descriptor of the log file + * @logCtxt: the domain log context * @msg: pointer to buffer to store the read messages in - * @off: Offset to start reading from * * Reads log of a qemu VM. Skips messages not produced by qemu or irrelevant * messages. Returns returns 0 on success or -1 on error */ static int -qemuProcessReadLog(int fd, off_t offset, char **msg) +qemuProcessReadLog(qemuDomainLogContextPtr logCtxt, char **msg) { char *buf; - size_t buflen = 1024 * 128; ssize_t got; char *eol; char *filter_next;
- /* Best effort jump to start of messages */ - ignore_value(lseek(fd, offset, SEEK_SET)); - - if (VIR_ALLOC_N(buf, buflen) < 0) - return -1; - - got = saferead(fd, buf, buflen - 1); - if (got < 0) { - virReportSystemError(errno, "%s", - _("Unable to read from log file")); + if ((got = qemuDomainLogContextRead(logCtxt, &buf)) < 0) return -1; - } - - buf[got] = '\0';
/* Filter out debug messages from intermediate libvirt process */ filter_next = buf; @@ -1672,24 +1676,24 @@ qemuProcessReadLog(int fd, off_t offset, char **msg) } }
- if (buf[got - 1] == '\n') { + if (got > 0 && + buf[got - 1] == '\n') { buf[got - 1] = '\0'; got--; } - VIR_SHRINK_N(buf, buflen, buflen - got - 1); + ignore_value(VIR_REALLOC_N_QUIET(buf, got + 1)); *msg = buf;
This is where I first saw Coverity erroneously complaining filter_next is leaked... If I set filter_buf = NULL after the while loop there's no complaint. Very strange.
return 0; }
ACK - as long as @destroy is noted and your call on other comments. John

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 | 153 ++++++++++++++++++++++++++----------- src/qemu/test_libvirtd_qemu.aug.in | 1 + 7 files changed, 145 insertions(+), 46 deletions(-) diff --git a/cfg.mk b/cfg.mk index 2a23b33..e35c79b 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..4fa5e8a 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 2417cb7..466b5b7 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -41,6 +41,7 @@ #include "virstring.h" #include "virthreadjob.h" #include "viratomic.h" +#include "logging/log_manager.h" #include "storage/storage_driver.h" @@ -80,8 +81,12 @@ VIR_ENUM_IMPL(qemuDomainAsyncJob, QEMU_ASYNC_JOB_LAST, struct _qemuDomainLogContext { int refs; int writefd; - int readfd; + int readfd; /* Only used if manager == NULL */ off_t pos; + ino_t inode; /* Only used if manager != NULL */ + unsigned char uuid[VIR_UUID_BUFLEN]; /* Only used if manager != NULL */ + char *name; /* Only used if manager != NULL */ + virLogManagerPtr manager; }; const char * @@ -2260,46 +2265,68 @@ qemuDomainLogContextPtr qemuDomainLogContextNew(virQEMUDriverPtr driver, if (VIR_ALLOC(ctxt) < 0) goto error; + VIR_DEBUG("Context new %p stdioLogD=%d", ctxt, cfg->stdioLogD); ctxt->writefd = -1; ctxt->readfd = -1; virAtomicIntSet(&ctxt->refs, 1); - if (virAsprintf(&logfile, "%s/%s.log", cfg->logDir, vm->def->name) < 0) - goto error; + if (cfg->stdioLogD) { + ctxt->manager = virLogManagerNew(virQEMUDriverIsPrivileged(driver)); + if (!ctxt->manager) + goto error; - if ((ctxt->writefd = open(logfile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR)) < 0) { - virReportSystemError(errno, _("failed to create logfile %s"), - logfile); - goto error; - } - if (virSetCloseExec(ctxt->writefd) < 0) { - virReportSystemError(errno, _("failed to set close-on-exec flag on %s"), - logfile); - goto error; - } + if (VIR_STRDUP(ctxt->name, vm->def->name) < 0) + goto error; - /* For unprivileged startup we must truncate the file since - * we can't rely on logrotate. We don't use O_TRUNC since - * it is better for SELinux policy if we truncate afterwards */ - if (mode == QEMU_DOMAIN_LOG_CONTEXT_MODE_START && - !virQEMUDriverIsPrivileged(driver) && - ftruncate(ctxt->writefd, 0) < 0) { - virReportSystemError(errno, _("failed to truncate %s"), - logfile); - goto error; - } + memcpy(ctxt->uuid, vm->def->uuid, VIR_UUID_BUFLEN); - if (mode == QEMU_DOMAIN_LOG_CONTEXT_MODE_START) { - if ((ctxt->readfd = open(logfile, O_RDONLY, S_IRUSR | S_IWUSR)) < 0) { - virReportSystemError(errno, _("failed to open logfile %s"), + ctxt->writefd = virLogManagerDomainOpenLogFile(ctxt->manager, + "qemu", + vm->def->uuid, + vm->def->name, + 0, + &ctxt->inode, + &ctxt->pos); + if (ctxt->writefd < 0) + goto error; + } else { + if (virAsprintf(&logfile, "%s/%s.log", cfg->logDir, vm->def->name) < 0) + goto error; + + if ((ctxt->writefd = open(logfile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR)) < 0) { + virReportSystemError(errno, _("failed to create logfile %s"), logfile); goto error; } - if (virSetCloseExec(ctxt->readfd) < 0) { + if (virSetCloseExec(ctxt->writefd) < 0) { virReportSystemError(errno, _("failed to set close-on-exec flag on %s"), logfile); goto error; } + + /* For unprivileged startup we must truncate the file since + * we can't rely on logrotate. We don't use O_TRUNC since + * it is better for SELinux policy if we truncate afterwards */ + if (mode == QEMU_DOMAIN_LOG_CONTEXT_MODE_START && + !virQEMUDriverIsPrivileged(driver) && + ftruncate(ctxt->writefd, 0) < 0) { + virReportSystemError(errno, _("failed to truncate %s"), + logfile); + goto error; + } + + if (mode == QEMU_DOMAIN_LOG_CONTEXT_MODE_START) { + if ((ctxt->readfd = open(logfile, O_RDONLY, S_IRUSR | S_IWUSR)) < 0) { + virReportSystemError(errno, _("failed to open logfile %s"), + logfile); + goto error; + } + if (virSetCloseExec(ctxt->readfd) < 0) { + virReportSystemError(errno, _("failed to set close-on-exec flag on %s"), + logfile); + goto error; + } + } } virObjectUnref(cfg); @@ -2323,9 +2350,10 @@ int qemuDomainLogContextWrite(qemuDomainLogContextPtr ctxt, if (virVasprintf(&message, fmt, argptr) < 0) goto cleanup; - if (lseek(ctxt->writefd, 0, SEEK_END) < 0) { + if (!ctxt->manager && + lseek(ctxt->writefd, 0, SEEK_END) < 0) { virReportSystemError(errno, "%s", - _("Unable to see to end of domain logfile")); + _("Unable to seek to end of domain logfile")); goto cleanup; } if (safewrite(ctxt->writefd, message, strlen(message)) < 0) { @@ -2346,29 +2374,51 @@ int qemuDomainLogContextWrite(qemuDomainLogContextPtr ctxt, ssize_t qemuDomainLogContextRead(qemuDomainLogContextPtr ctxt, char **msg) { + VIR_DEBUG("Context read %p manager=%p inode=%llu pos=%llu", + ctxt, ctxt->manager, + (unsigned long long)ctxt->inode, + (unsigned long long)ctxt->pos); char *buf; - size_t buflen = 1024 * 128; - ssize_t got; + size_t buflen; + if (ctxt->manager) { + buf = virLogManagerDomainReadLogFile(ctxt->manager, + "qemu", + ctxt->uuid, + ctxt->name, + ctxt->inode, + ctxt->pos, + 1024 * 128, + 0); + if (!buf) + return -1; + buflen = strlen(buf); + } else { + ssize_t got; - /* Best effort jump to start of messages */ - ignore_value(lseek(ctxt->readfd, ctxt->pos, SEEK_SET)); + buflen = 1024 * 128; - if (VIR_ALLOC_N(buf, buflen) < 0) - return -1; + /* Best effort jump to start of messages */ + ignore_value(lseek(ctxt->readfd, ctxt->pos, SEEK_SET)); - got = saferead(ctxt->readfd, buf, buflen - 1); - if (got < 0) { - virReportSystemError(errno, "%s", - _("Unable to read from log file")); - return -1; - } + if (VIR_ALLOC_N(buf, buflen) < 0) + return -1; - buf[got] = '\0'; + got = saferead(ctxt->readfd, buf, buflen - 1); + if (got < 0) { + virReportSystemError(errno, "%s", + _("Unable to read from log file")); + return -1; + } + + buf[got] = '\0'; + + ignore_value(VIR_REALLOC_N_QUIET(buf, got + 1)); + buflen = got; + } - ignore_value(VIR_REALLOC_N_QUIET(buf, got + 1)); *msg = buf; - return got; + return buflen; } @@ -2380,12 +2430,22 @@ int qemuDomainLogContextGetWriteFD(qemuDomainLogContextPtr ctxt) void qemuDomainLogContextMarkPosition(qemuDomainLogContextPtr ctxt) { - ctxt->pos = lseek(ctxt->writefd, 0, SEEK_END); + if (ctxt->manager) + virLogManagerDomainGetLogFilePosition(ctxt->manager, + "qemu", + ctxt->uuid, + ctxt->name, + 0, + &ctxt->inode, + &ctxt->pos); + else + ctxt->pos = lseek(ctxt->writefd, 0, SEEK_END); } void qemuDomainLogContextRef(qemuDomainLogContextPtr ctxt) { + VIR_DEBUG("Context ref %p", ctxt); virAtomicIntInc(&ctxt->refs); } @@ -2398,9 +2458,12 @@ void qemuDomainLogContextFree(qemuDomainLogContextPtr ctxt) return; lastRef = virAtomicIntDecAndTest(&ctxt->refs); + VIR_DEBUG("Context free %p lastref=%d", ctxt, lastRef); if (!lastRef) return; + virLogManagerFree(ctxt->manager); + VIR_FREE(ctxt->name); VIR_FORCE_CLOSE(ctxt->writefd); VIR_FORCE_CLOSE(ctxt->readfd); VIR_FREE(ctxt); 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 11/12/2015 12:19 PM, Daniel P. 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 | 153 ++++++++++++++++++++++++++----------- src/qemu/test_libvirtd_qemu.aug.in | 1 + 7 files changed, 145 insertions(+), 46 deletions(-)
ACK John
participants (3)
-
Daniel P. Berrange
-
John Ferlan
-
Peter Krempa