
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