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