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