diff --git a/.gitignore b/.gitignore index 2d52a8fb06..7f3e253aba 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 fd9fd75ae5..8530e1789c 100644 --- a/cfg.mk +++ b/cfg.mk @@ -1121,7 +1121,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$$ @@ -1156,7 +1156,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 f716cb97ea..0539e48991 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 ac46da5744..8420d1107c 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 401ac6f4e3..33bc258695 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 ee082ec2bb..3323bd1383 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 0000000000..c3d7cbc83c --- /dev/null +++ b/src/logging/log_daemon.c @@ -0,0 +1,1172 @@ +/* + * 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 + * . + * + * Author: Daniel P. Berrange + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + + +#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 log daemon", + "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; + + virMutexDestroy(&logd->lock); + 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); + VIR_FREE(rundir); + 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 */ +} + + +static int +virLogDaemonSetupLogging(virLogDaemonConfigPtr config, + bool privileged, + bool verbose, + bool godaemon) +{ + virLogReset(); + + /* + * virtlogd'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); + 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: + virLogDaemonClientFree(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 Exit after timeout period.\n" + " -f | --config Configuration file.\n" + " -V | --version Display version information.\n" + " -p | --pid-file Change name of PID file.\n" + "\n" + "libvirt log 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")); + } +} + +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, "df: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; + umask(old_umask); + 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 0000000000..a1531604d5 --- /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 + * . + * + * Author: Daniel P. Berrange + */ + +#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 0000000000..9e729fa24a --- /dev/null +++ b/src/logging/log_daemon_config.c @@ -0,0 +1,202 @@ +/* + * 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 + * . + * + * Author: Daniel P. Berrange + */ + +#include + +#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. + */ +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 0000000000..24cc631b7d --- /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 + * . + * + * Author: Daniel P. Berrange + */ + +#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 0000000000..98df178b76 --- /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 + * . + * + * Author: Daniel P. Berrange + */ + +#include + +#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 0000000000..af3e3b47a1 --- /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 + * . + * + * Author: Daniel P. Berrange + */ + +#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 0000000000..1481c301fa --- /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 = 4194304; + +/* A long string, which may NOT be NULL. */ +typedef string virLogManagerProtocolNonNullString; + +/* 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 0000000000..b12f676b6f --- /dev/null +++ b/src/logging/test_virtlogd.aug.in @@ -0,0 +1,10 @@ +module Test_virtlogd = + let conf = "log_level = 3 +log_filters=\"3:remote 4:event\" +log_outputs=\"3:syslog:virtlogd\" +" + + test Virtlogd.lns get conf = + { "log_level" = "3" } + { "log_filters" = "3:remote 4:event" } + { "log_outputs" = "3:syslog:virtlogd" } diff --git a/src/logging/virtlogd.aug b/src/logging/virtlogd.aug new file mode 100644 index 0000000000..eefba5b8a2 --- /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 0000000000..abb3633271 --- /dev/null +++ b/src/logging/virtlogd.conf @@ -0,0 +1,59 @@ +# 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 +# x:journald +# ouput to the systemd journal +# 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" +# + +# The maximum number of concurrent client connections to allow +# over all sockets combined. +#max_clients = 1024 diff --git a/src/logging/virtlogd.init.in b/src/logging/virtlogd.init.in new file mode 100644 index 0000000000..14082367ae --- /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-generic.html#INITSCRCOMCONV +# +### 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 0000000000..ce57f95dfc --- /dev/null +++ b/src/logging/virtlogd.pod.in @@ -0,0 +1,167 @@ +=head1 NAME + +virtlogd - libvirt log management daemon + +=head1 SYNOPSIS + +B [ -dvV ] [-t timeout] [ -f config_file ] [ -p pid_file ] + +B --version + +=head1 DESCRIPTION + +The B 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. By maintaining the logs in a +standalone daemon, the main libvirtd daemon can be restarted without risk +of losing logs. The B 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 + +Use this configuration file, overriding the default value. + +=item B<-t, --timeout> I + +Automatically shutdown after I have elapsed with +no active console log. + +=item B<-p, --pid-file> I + +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 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. + +=over + +=item F + +The default configuration file used by virtlogd, unless overridden on the +command line using the B<-f>|B<--config> option. + +=item F + +The sockets libvirtd will use. + +=item F + +The PID file to use, unless overridden by the B<-p>|B<--pid-file> option. + +=back + +=head2 When run as B. + +=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 + +=item or, + +B<> + +=item b) the bug tracker + +L + +=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, L + +=cut diff --git a/src/logging/virtlogd.service.in b/src/logging/virtlogd.service.in new file mode 100644 index 0000000000..a264d3ac61 --- /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 0000000000..724976dc35 --- /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 0000000000..5886f35110 --- /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 6dc05f4801..098211ac1f 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", )