/* * 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 * . * * Author: Daniel P. Berrange */ #include #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 "viruuid.h" #include #include #include "configmake.h" VIR_LOG_INIT("logging.log_handler"); #define VIR_FROM_THIS VIR_FROM_LOGGING #define DEFAULT_MODE 0600 typedef struct _virLogHandlerLogFile virLogHandlerLogFile; typedef virLogHandlerLogFile *virLogHandlerLogFilePtr; struct _virLogHandlerLogFile { virRotatingFileWriterPtr file; int watch; int pipefd; /* Read from QEMU via this */ char *driver; unsigned char domuuid[VIR_UUID_BUFLEN]; char *domname; }; struct _virLogHandler { virObjectLockable parent; bool privileged; size_t max_size; size_t max_backups; virLogHandlerLogFilePtr *files; size_t nfiles; virLogHandlerShutdownInhibitor inhibitor; void *opaque; }; 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->driver); VIR_FREE(file->domname); 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); virObjectUnlock(handler); 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: handler->inhibitor(false, handler->opaque); virLogHandlerLogFileClose(handler, logfile); virObjectUnlock(handler); } virLogHandlerPtr virLogHandlerNew(bool privileged, size_t max_size, size_t max_backups, virLogHandlerShutdownInhibitor inhibitor, void *opaque) { virLogHandlerPtr handler; if (virLogHandlerInitialize() < 0) goto error; if (!(handler = virObjectLockableNew(virLogHandlerClass))) goto error; handler->privileged = privileged; handler->max_size = max_size; handler->max_backups = max_backups; handler->inhibitor = inhibitor; handler->opaque = opaque; return handler; error: return NULL; } static virLogHandlerLogFilePtr virLogHandlerLogFilePostExecRestart(virLogHandlerPtr handler, virJSONValuePtr object) { virLogHandlerLogFilePtr file; const char *path; const char *domuuid; const char *tmp; if (VIR_ALLOC(file) < 0) return NULL; handler->inhibitor(true, handler->opaque); if ((path = virJSONValueObjectGetString(object, "path")) == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Missing 'path' field in JSON document")); goto error; } if ((tmp = virJSONValueObjectGetString(object, "driver")) == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Missing 'driver' in JSON document")); goto error; } if (VIR_STRDUP(file->driver, tmp) < 0) goto error; if ((tmp = virJSONValueObjectGetString(object, "domname")) == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Missing 'domname' in JSON document")); goto error; } if (VIR_STRDUP(file->domname, tmp) < 0) goto error; if ((domuuid = virJSONValueObjectGetString(object, "domuuid")) == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Missing 'domuuid' in JSON document")); goto error; } if (virUUIDParse(domuuid, file->domuuid) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Malformed 'domuuid' in JSON document")); goto error; } if ((file->file = virRotatingFileWriterNew(path, handler->max_size, handler->max_backups, false, DEFAULT_MODE)) == NULL) goto error; if (virJSONValueObjectGetNumberInt(object, "pipefd", &file->pipefd) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Missing '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: handler->inhibitor(false, handler->opaque); virLogHandlerLogFileFree(file); return NULL; } virLogHandlerPtr virLogHandlerNewPostExecRestart(virJSONValuePtr object, bool privileged, size_t max_size, size_t max_backups, virLogHandlerShutdownInhibitor inhibitor, void *opaque) { virLogHandlerPtr handler; virJSONValuePtr files; ssize_t n; size_t i; if (!(handler = virLogHandlerNew(privileged, max_size, max_backups, inhibitor, opaque))) 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(handler, 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++) { handler->inhibitor(false, handler->opaque); virLogHandlerLogFileFree(handler->files[i]); } VIR_FREE(handler->files); } int virLogHandlerDomainOpenLogFile(virLogHandlerPtr handler, const char *driver, const unsigned char *domuuid, const char *domname, const char *path, bool trunc, ino_t *inode, off_t *offset) { size_t i; virLogHandlerLogFilePtr file = NULL; int pipefd[2] = { -1, -1 }; virObjectLock(handler); handler->inhibitor(true, handler->opaque); 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; memcpy(file->domuuid, domuuid, VIR_UUID_BUFLEN); if (VIR_STRDUP(file->driver, driver) < 0 || VIR_STRDUP(file->domname, domname) < 0) goto error; if ((file->file = virRotatingFileWriterNew(path, handler->max_size, handler->max_backups, trunc, DEFAULT_MODE)) == 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; } *inode = virRotatingFileWriterGetINode(file->file); *offset = virRotatingFileWriterGetOffset(file->file); virObjectUnlock(handler); return pipefd[1]; error: VIR_FORCE_CLOSE(pipefd[0]); VIR_FORCE_CLOSE(pipefd[1]); handler->inhibitor(false, handler->opaque); virLogHandlerLogFileFree(file); virObjectUnlock(handler); return -1; } int virLogHandlerDomainGetLogFilePosition(virLogHandlerPtr handler, const char *path, unsigned int flags, ino_t *inode, off_t *offset) { virLogHandlerLogFilePtr file = NULL; int ret = -1; size_t i; virCheckFlags(0, -1); virObjectLock(handler); 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 %s"), path); goto cleanup; } *inode = virRotatingFileWriterGetINode(file->file); *offset = virRotatingFileWriterGetOffset(file->file); ret = 0; cleanup: virObjectUnlock(handler); return ret; } char * virLogHandlerDomainReadLogFile(virLogHandlerPtr handler, const char *path, ino_t inode, off_t offset, size_t maxlen, unsigned int flags) { virRotatingFileReaderPtr file = NULL; char *data = NULL; ssize_t got; virCheckFlags(0, NULL); virObjectLock(handler); if (!(file = virRotatingFileReaderNew(path, handler->max_backups))) goto error; if (virRotatingFileReaderSeek(file, inode, offset) < 0) goto error; if (VIR_ALLOC_N(data, maxlen + 1) < 0) goto error; got = virRotatingFileReaderConsume(file, data, maxlen); if (got < 0) goto error; data[got] = '\0'; virRotatingFileReaderFree(file); virObjectUnlock(handler); return data; error: VIR_FREE(data); virRotatingFileReaderFree(file); virObjectUnlock(handler); return NULL; } int virLogHandlerDomainAppendLogFile(virLogHandlerPtr handler, const char *driver ATTRIBUTE_UNUSED, const unsigned char *domuuid ATTRIBUTE_UNUSED, const char *domname ATTRIBUTE_UNUSED, const char *path, const char *message, unsigned int flags) { size_t i; virRotatingFileWriterPtr writer = NULL; virRotatingFileWriterPtr newwriter = NULL; int ret = -1; virCheckFlags(0, -1); VIR_DEBUG("Appending to log '%s' message: '%s'", path, message); virObjectLock(handler); for (i = 0; i < handler->nfiles; i++) { if (STREQ(virRotatingFileWriterGetPath(handler->files[i]->file), path)) { writer = handler->files[i]->file; break; } } if (!writer) { if (!(newwriter = virRotatingFileWriterNew(path, handler->max_size, handler->max_backups, false, DEFAULT_MODE))) goto cleanup; writer = newwriter; } if (virRotatingFileWriterAppend(writer, message, strlen(message)) < 0) goto cleanup; ret = 0; cleanup: virRotatingFileWriterFree(newwriter); virObjectUnlock(handler); return ret; } virJSONValuePtr virLogHandlerPreExecRestart(virLogHandlerPtr handler) { virJSONValuePtr ret = virJSONValueNewObject(); virJSONValuePtr files; size_t i; char domuuid[VIR_UUID_STRING_BUFLEN]; 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 (virJSONValueObjectAppendString(file, "driver", handler->files[i]->driver) < 0) goto error; if (virJSONValueObjectAppendString(file, "domname", handler->files[i]->domname) < 0) goto error; virUUIDFormat(handler->files[i]->domuuid, domuuid); if (virJSONValueObjectAppendString(file, "domuuid", domuuid) < 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; }