From: "Daniel P. Berrange" <berrange@xxxxxxxxxx> The virtlockd daemon will maintain locks on behalf of libvirtd. There are two reasons for it to be separate - Avoid risk of other libvirtd threads accidentally releasing fcntl() locks by opening + closing a file that is locked - Ensure locks can be preserved across libvirtd restarts. virtlockd will need to be able to re-exec itself while maintaining locks. This is simpler to achieve if its sole job is maintaining locks Signed-off-by: Daniel P. Berrange <berrange@xxxxxxxxxx> --- .gitignore | 2 + cfg.mk | 6 +- libvirt.spec.in | 7 + po/POTFILES.in | 2 + src/Makefile.am | 89 +++- src/locking/lock_daemon.c | 850 +++++++++++++++++++++++++++++++++++++++ src/locking/lock_daemon.h | 43 ++ src/locking/lock_daemon_config.c | 193 +++++++++ src/locking/lock_daemon_config.h | 50 +++ src/locking/virtlockd.init.in | 93 +++++ src/locking/virtlockd.sysconf | 3 + 11 files changed, 1334 insertions(+), 4 deletions(-) create mode 100644 src/locking/lock_daemon.c create mode 100644 src/locking/lock_daemon.h create mode 100644 src/locking/lock_daemon_config.c create mode 100644 src/locking/lock_daemon_config.h create mode 100644 src/locking/virtlockd.init.in create mode 100644 src/locking/virtlockd.sysconf diff --git a/.gitignore b/.gitignore index 0dadd21..1e3a624 100644 --- a/.gitignore +++ b/.gitignore @@ -123,6 +123,8 @@ /src/test_libvirt*.aug /src/util/virkeymaps.h /src/virt-aa-helper +/src/virtlockd +/src/virtlockd.init /tests/*.log /tests/*.pid /tests/*xml2*test diff --git a/cfg.mk b/cfg.mk index f218eb6..95a1d3a 100644 --- a/cfg.mk +++ b/cfg.mk @@ -655,6 +655,8 @@ sc_prohibit_cross_inclusion: @for dir in $(cross_dirs); do \ case $$dir in \ util/) safe="util";; \ + locking/) \ + safe="($$dir|util|conf|rpc)";; \ cpu/ | locking/ | network/ | rpc/ | security/) \ safe="($$dir|util|conf)";; \ xenapi/ | xenxs/ ) safe="($$dir|util|conf|xen)";; \ @@ -743,7 +745,7 @@ $(srcdir)/src/remote/remote_client_bodies.h: $(srcdir)/src/remote/remote_protoco # List all syntax-check exemptions: exclude_file_name_regexp--sc_avoid_strcase = ^tools/virsh\.h$$ -_src1=libvirt|fdstream|qemu/qemu_monitor|util/(command|util)|xen/xend_internal|rpc/virnetsocket|lxc/lxc_controller +_src1=libvirt|fdstream|qemu/qemu_monitor|util/(command|util)|xen/xend_internal|rpc/virnetsocket|lxc/lxc_controller|locking/lock_daemon exclude_file_name_regexp--sc_avoid_write = \ ^(src/($(_src1))|daemon/libvirtd|tools/console|tests/(shunload|virnettlscontext)test)\.c$$ @@ -776,7 +778,7 @@ exclude_file_name_regexp--sc_prohibit_close = \ exclude_file_name_regexp--sc_prohibit_empty_lines_at_EOF = \ (^tests/(qemuhelp|nodeinfo)data/|\.(gif|ico|png|diff)$$) -_src2=src/(util/command|libvirt|lxc/lxc_controller) +_src2=src/(util/command|libvirt|lxc/lxc_controller|locking/lock_daemon) exclude_file_name_regexp--sc_prohibit_fork_wrappers = \ (^($(_src2)|tests/testutils|daemon/libvirtd)\.c$$) diff --git a/libvirt.spec.in b/libvirt.spec.in index d6e1fbe..e12fca4 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -1661,9 +1661,11 @@ fi %{_unitdir}/libvirtd.service %else %{_sysconfdir}/rc.d/init.d/libvirtd +%{_sysconfdir}/rc.d/init.d/virtlockd %endif %doc daemon/libvirtd.upstart %config(noreplace) %{_sysconfdir}/sysconfig/libvirtd +%config(noreplace) %{_sysconfdir}/sysconfig/virtlockd %config(noreplace) %{_sysconfdir}/libvirt/libvirtd.conf %if 0%{?fedora} >= 14 || 0%{?rhel} >= 6 %config(noreplace) %{_sysconfdir}/sysctl.d/libvirtd @@ -1730,6 +1732,10 @@ rm -f $RPM_BUILD_ROOT%{_sysconfdir}/sysctl.d/libvirtd %dir %attr(0755, root, root) %{_localstatedir}/lib/libvirt/dnsmasq/ %endif +%if %{with_libvirtd} +%dir %attr(0755, root, root) %{_libdir}/libvirt/lock-driver +%endif + %if %{with_qemu} %{_datadir}/augeas/lenses/libvirtd_qemu.aug %{_datadir}/augeas/lenses/tests/test_libvirtd_qemu.aug @@ -1763,6 +1769,7 @@ rm -f $RPM_BUILD_ROOT%{_sysconfdir}/sysctl.d/libvirtd %attr(0755, root, root) %{_libexecdir}/libvirt_iohelper %attr(0755, root, root) %{_sbindir}/libvirtd +%attr(0755, root, root) %{_sbindir}/virtlockd %{_mandir}/man8/libvirtd.8* diff --git a/po/POTFILES.in b/po/POTFILES.in index 51a1f5c..34c688c 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -48,6 +48,8 @@ src/interface/interface_backend_udev.c src/internal.h src/libvirt.c src/libvirt-qemu.c +src/locking/lock_daemon.c +src/locking/lock_daemon_config.c src/locking/lock_driver_sanlock.c src/locking/lock_manager.c src/locking/sanlock_helper.c diff --git a/src/Makefile.am b/src/Makefile.am index 6d2816d..6a66efd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -148,6 +148,13 @@ LOCK_DRIVER_SANLOCK_SOURCES = \ LOCK_DRIVER_SANLOCK_HELPER_SOURCES = \ locking/sanlock_helper.c +LOCK_DAEMON_SOURCES = \ + locking/lock_daemon.h \ + locking/lock_daemon.c \ + locking/lock_daemon_config.h \ + locking/lock_daemon_config.c \ + $(NULL) + 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 \ @@ -1508,6 +1515,76 @@ libvirt_qemu_la_CFLAGS = $(AM_CFLAGS) libvirt_qemu_la_LIBADD = libvirt.la $(CYGWIN_EXTRA_LIBADD) EXTRA_DIST += $(LIBVIRT_QEMU_SYMBOL_FILE) +if WITH_LIBVIRTD +sbin_PROGRAMS = virtlockd + +virtlockd_SOURCES = $(LOCK_DAEMON_SOURCES) +virtlockd_CFLAGS = \ + $(AM_CFLAGS) \ + $(NULL) +virtlockd_LDFLAGS = \ + $(AM_LDFLAGS) \ + $(CYGWIN_EXTRA_LDFLAGS) \ + $(MINGW_EXTRA_LDFLAGS) \ + $(NULL) +virtlockd_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 +virtlockd_LDADD += libvirt_probes.lo +endif + +else +EXTRA_DIST += $(LOCK_DAEMON_SOURCES) +endif + +EXTRA_DIST += locking/virtlockd.sysconf + +install-sysconfig: + mkdir -p $(DESTDIR)$(sysconfdir)/sysconfig + $(INSTALL_DATA) $(srcdir)/locking/virtlockd.sysconf \ + $(DESTDIR)$(sysconfdir)/sysconfig/virtlockd + +uninstall-sysconfig: + rm -f $(DESTDIR)$(sysconfdir)/sysconfig/virtlockd + +EXTRA_DIST += locking/virtlockd.init.in + +if WITH_LIBVIRTD +if LIBVIRT_INIT_SCRIPT_RED_HAT +install-init:: virtlockd.init install-sysconfig + mkdir -p $(DESTDIR)$(sysconfdir)/rc.d/init.d + $(INSTALL_SCRIPT) virtlockd.init \ + $(DESTDIR)$(sysconfdir)/rc.d/init.d/virtlockd + +uninstall-init:: uninstall-sysconfig + rm -f $(DESTDIR)$(sysconfdir)/rc.d/init.d/libvirtd + +BUILT_SOURCES += virtlockd.init +else +install-init:: +uninstall-init:: +endif +else +install-init:: +uninstall-init:: +endif + +virtlockd.init: locking/virtlockd.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 $@ + + + if HAVE_SANLOCK lockdriverdir = $(libdir)/libvirt/lock-driver lockdriver_LTLIBRARIES = sanlock.la @@ -1745,7 +1822,11 @@ endif endif EXTRA_DIST += $(SECURITY_DRIVER_APPARMOR_HELPER_SOURCES) -install-data-local: +install-data-local: install-init +if WITH_LIBVIRTD + $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/libvirt/lockd" + $(MKDIR_P) "$(DESTDIR)$(localstatedir)/run/libvirt/lockd" +endif $(MKDIR_P) "$(DESTDIR)$(localstatedir)/cache/libvirt" $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/libvirt/images" $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/libvirt/filesystems" @@ -1794,7 +1875,11 @@ if WITH_NETWORK $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/autostart/default.xml endif -uninstall-local:: +uninstall-local:: uninstall-init +if WITH_LIBVIRTD + rmdir "$(DESTDIR)$(localstatedir)/lib/libvirt/lockd" ||: + rmdir "$(DESTDIR)$(localstatedir)/run/libvirt/lockd" ||: +endif rmdir "$(DESTDIR)$(localstatedir)/cache/libvirt" ||: rmdir "$(DESTDIR)$(localstatedir)/lib/libvirt/images" ||: rmdir "$(DESTDIR)$(localstatedir)/lib/libvirt/filesystems" ||: diff --git a/src/locking/lock_daemon.c b/src/locking/lock_daemon.c new file mode 100644 index 0000000..f821d2e --- /dev/null +++ b/src/locking/lock_daemon.c @@ -0,0 +1,850 @@ +/* + * lock_daemon.c: lock management daemon + * + * Copyright (C) 2006-2012 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; If not, see + * <http://www.gnu.org/licenses/>. + * + * Author: Daniel P. Berrange <berrange@xxxxxxxxxx> + */ + +#include <config.h> + +#include <unistd.h> +#include <fcntl.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <getopt.h> +#include <stdlib.h> +#include <locale.h> + + +#include "lock_daemon.h" +#include "lock_daemon_config.h" +#include "util.h" +#include "virfile.h" +#include "virpidfile.h" +#include "virterror_internal.h" +#include "logging.h" +#include "memory.h" +#include "conf.h" +#include "rpc/virnetserver.h" +#include "virrandom.h" +#include "virhash.h" + +#include "configmake.h" + +#define VIR_FROM_THIS VIR_FROM_LOCKING + +struct _virLockDaemon { + virMutex lock; + virNetServerPtr srv; +}; + +virLockDaemonPtr lockDaemon = NULL; + +enum { + VIR_LOCK_DAEMON_ERR_NONE = 0, + VIR_LOCK_DAEMON_ERR_PIDFILE, + VIR_LOCK_DAEMON_ERR_RUNDIR, + VIR_LOCK_DAEMON_ERR_INIT, + VIR_LOCK_DAEMON_ERR_SIGNAL, + VIR_LOCK_DAEMON_ERR_PRIVS, + VIR_LOCK_DAEMON_ERR_NETWORK, + VIR_LOCK_DAEMON_ERR_CONFIG, + VIR_LOCK_DAEMON_ERR_HOOKS, + + VIR_LOCK_DAEMON_ERR_LAST +}; + +VIR_ENUM_DECL(virDaemonErr) +VIR_ENUM_IMPL(virDaemonErr, VIR_LOCK_DAEMON_ERR_LAST, + "Initialization successful", + "Unable to obtain pidfile", + "Unable to create rundir", + "Unable to initialize libvirt", + "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"); + +static void * +virLockDaemonClientNew(virNetServerClientPtr client, + void *opaque); +static void +virLockDaemonClientFree(void *opaque); + +static void +virLockDaemonFree(virLockDaemonPtr lockd) +{ + if (!lockd) + return; + + virObjectUnref(lockd->srv); + + VIR_FREE(lockd); +} + + +static virLockDaemonPtr +virLockDaemonNew(bool privileged) +{ + virLockDaemonPtr lockd; + + if (VIR_ALLOC(lockd) < 0) { + virReportOOMError(); + return NULL; + } + + if (virMutexInit(&lockd->lock) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to initialize mutex")); + VIR_FREE(lockd); + return NULL; + } + + if (!(lockd->srv = virNetServerNew(1, 1, 0, 20, + -1, 0, + false, NULL, + virLockDaemonClientNew, + NULL, + virLockDaemonClientFree, + (void*)(intptr_t)(privileged ? 0x1 : 0x0)))) + goto error; + + return lockd; + +error: + virLockDaemonFree(lockd); + return NULL; +} + + +static int +virLockDaemonForkIntoBackground(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 +virLockDaemonPidFilePath(bool privileged, + char **pidfile) +{ + if (privileged) { + if (!(*pidfile = strdup(LOCALSTATEDIR "/run/virtlockd.pid"))) + goto no_memory; + } else { + char *rundir = NULL; + mode_t old_umask; + + if (!(rundir = virGetUserRuntimeDirectory())) + goto error; + + old_umask = umask(077); + if (virFileMakePath(rundir) < 0) { + umask(old_umask); + goto error; + } + umask(old_umask); + + if (virAsprintf(pidfile, "%s/virtlockd.pid", rundir) < 0) { + VIR_FREE(rundir); + goto no_memory; + } + + VIR_FREE(rundir); + } + + return 0; + +no_memory: + virReportOOMError(); +error: + return -1; +} + + +static int +virLockDaemonUnixSocketPaths(bool privileged, + char **sockfile) +{ + if (privileged) { + if (!(*sockfile = strdup(LOCALSTATEDIR "/run/libvirt/virtlockd-sock"))) + goto no_memory; + } else { + char *rundir = NULL; + mode_t old_umask; + + if (!(rundir = virGetUserRuntimeDirectory())) + goto error; + + old_umask = umask(077); + if (virFileMakePath(rundir) < 0) { + umask(old_umask); + goto error; + } + umask(old_umask); + + if (virAsprintf(sockfile, "%s/virtlockd-sock", rundir) < 0) { + VIR_FREE(rundir); + goto no_memory; + } + + VIR_FREE(rundir); + } + return 0; + +no_memory: + virReportOOMError(); +error: + return -1; +} + + +static void +virLockDaemonErrorHandler(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 +virLockDaemonSetupLogging(virLockDaemonConfigPtr 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(); + + virLogSetBufferSize(config->log_buffer_size); + + 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 no_memory; + 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/virtlockd.log", + virLogGetDefaultPriority(), + LOCALSTATEDIR) == -1) + goto no_memory; + } else { + char *logdir = virGetUserCacheDirectory(); + mode_t old_umask; + + if (!logdir) + goto error; + + old_umask = umask(077); + if (virFileMakePath(logdir) < 0) { + umask(old_umask); + goto error; + } + umask(old_umask); + + if (virAsprintf(&tmp, "%d:file:%s/virtlockd.log", + virLogGetDefaultPriority(), logdir) == -1) { + VIR_FREE(logdir); + goto no_memory; + } + VIR_FREE(logdir); + } + } else { + if (virAsprintf(&tmp, "%d:stderr", virLogGetDefaultPriority()) < 0) + goto no_memory; + } + virLogParseOutputs(tmp); + VIR_FREE(tmp); + } + + /* + * Command line override for --verbose + */ + if ((verbose) && (virLogGetDefaultPriority() > VIR_LOG_INFO)) + virLogSetDefaultPriority(VIR_LOG_INFO); + + return 0; + +no_memory: + virReportOOMError(); +error: + return -1; +} + + + +/* Display version information. */ +static void +virLockDaemonVersion(const char *argv0) +{ + printf("%s (%s) %s\n", argv0, PACKAGE_NAME, PACKAGE_VERSION); +} + +static void +virLockDaemonShutdownHandler(virNetServerPtr srv, + siginfo_t *sig ATTRIBUTE_UNUSED, + void *opaque ATTRIBUTE_UNUSED) +{ + virNetServerQuit(srv); +} + +static int +virLockDaemonSetupSignals(virNetServerPtr srv) +{ + if (virNetServerAddSignalHandler(srv, SIGINT, virLockDaemonShutdownHandler, NULL) < 0) + return -1; + if (virNetServerAddSignalHandler(srv, SIGQUIT, virLockDaemonShutdownHandler, NULL) < 0) + return -1; + if (virNetServerAddSignalHandler(srv, SIGTERM, virLockDaemonShutdownHandler, NULL) < 0) + return -1; + return 0; +} + +static int +virLockDaemonSetupNetworking(virNetServerPtr srv, const char *sock_path) +{ + virNetServerServicePtr svc; + + VIR_DEBUG("Setting up networking natively"); + + if (!(svc = virNetServerServiceNewUNIX(sock_path, 0700, 0, 0, false, 1, NULL))) + return -1; + + if (virNetServerAddService(srv, svc, NULL) < 0) { + virObjectUnref(svc); + return -1; + } + return 0; +} + + +static void +virLockDaemonClientFree(void *opaque) +{ + virLockDaemonClientPtr 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 * +virLockDaemonClientNew(virNetServerClientPtr client, + void *opaque) +{ + virLockDaemonClientPtr priv; + uid_t clientuid; + gid_t clientgid; + bool privileged = opaque != NULL; + + if (VIR_ALLOC(priv) < 0) { + virReportOOMError(); + return NULL; + } + + if (virMutexInit(&priv->lock) < 0) { + VIR_FREE(priv); + virReportOOMError(); + return NULL; + } + + if (virNetServerClientGetUNIXIdentity(client, + &clientuid, + &clientgid, + &priv->clientPid) < 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) { + virReportError(VIR_ERR_OPERATION_DENIED, + _("Disallowing client %llu with uid %llu"), + (unsigned long long)priv->clientPid, + (unsigned long long)clientuid); + goto error; + } + } else { + if (clientuid != 0) { + virReportError(VIR_ERR_OPERATION_DENIED, + _("Disallowing client %llu with uid %llu"), + (unsigned long long)priv->clientPid, + (unsigned long long)clientuid); + goto error; + } + } + + return priv; + +error: + virMutexDestroy(&priv->lock); + VIR_FREE(priv); + return NULL; +} + + +static void +virLockDaemonUsage(const char *argv0, bool privileged) +{ + fprintf(stderr, + _("\n" + "Usage:\n" + " %s [options]\n" + "\n" + "Options:\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" + " | --version Display version information.\n" + " -p | --pid-file <file> Change name of PID file.\n" + "\n" + "libvirt lock management daemon:\n"), argv0); + + if (privileged) { + fprintf(stderr, + _("\n" + " Default paths:\n" + "\n" + " Configuration file (unless overridden by -f):\n" + " %s/libvirt/virtlockd.conf\n" + "\n" + " Sockets:\n" + " %s/run/libvirt/virtlockd-sock\n" + "\n" + " PID file (unless overridden by -p):\n" + " %s/run/virtlockd.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/virtlockd.conf\n" + "\n" + " Sockets:\n" + " $XDG_RUNTIME_DIR/libvirt/virtlockd-sock\n" + "\n" + " PID file:\n" + " $XDG_RUNTIME_DIR/libvirt/virtlockd.pid\n" + "\n")); + } +} + +enum { + OPT_VERSION = 129 +}; + +#define MAX_LISTEN 5 +int main(int argc, char **argv) { + char *remote_config_file = NULL; + int statuswrite = -1; + int ret = 1; + int verbose = 0; + int godaemon = 0; + int timeout = 0; + char *run_dir = NULL; + char *pid_file = NULL; + int pid_file_fd = -1; + char *sock_file = NULL; + bool implicit_conf = false; + mode_t old_umask; + bool privileged = false; + virLockDaemonConfigPtr config = NULL; + + struct option opts[] = { + { "verbose", no_argument, &verbose, 1}, + { "daemon", no_argument, &godaemon, 1}, + { "config", required_argument, NULL, 'f'}, + { "timeout", required_argument, NULL, 't'}, + { "pid-file", required_argument, NULL, 'p'}, + { "version", no_argument, NULL, OPT_VERSION }, + { "help", no_argument, NULL, '?' }, + {0, 0, 0, 0} + }; + + privileged = getuid() == 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:v", 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) + timeout = -1; + break; + + case 'p': + VIR_FREE(pid_file); + if (!(pid_file = strdup(optarg))) + exit(EXIT_FAILURE); + break; + + case 'f': + VIR_FREE(remote_config_file); + if (!(remote_config_file = strdup(optarg))) + exit(EXIT_FAILURE); + break; + + case OPT_VERSION: + virLockDaemonVersion(argv[0]); + return 0; + + case '?': + virLockDaemonUsage(argv[0], privileged); + return 2; + + default: + fprintf(stderr, _("%s: internal error: unknown flag: %c\n"), + argv[0], c); + exit(EXIT_FAILURE); + } + } + + if (!(config = virLockDaemonConfigNew(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 (virLockDaemonConfigFilePath(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 && + virLockDaemonConfigLoadFile(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 (virLockDaemonSetupLogging(config, privileged, verbose, godaemon) < 0) { + VIR_ERROR(_("Can't initialize logging")); + exit(EXIT_FAILURE); + } + + if (!pid_file && + virLockDaemonPidFilePath(privileged, + &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 (virLockDaemonUnixSocketPaths(privileged, + &sock_file) < 0) { + VIR_ERROR(_("Can't determine socket paths")); + exit(EXIT_FAILURE); + } + VIR_DEBUG("Decided on socket paths '%s'", + sock_file); + + 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 = virLockDaemonForkIntoBackground(argv[0])) < 0) { + VIR_ERROR(_("Failed to fork as daemon: %s"), + virStrerror(errno, ebuf, sizeof(ebuf))); + goto cleanup; + } + } + + /* Ensure the rundir exists (on tmpfs on some systems) */ + if (privileged) { + run_dir = strdup(LOCALSTATEDIR "/run/libvirt"); + } else { + run_dir = virGetUserRuntimeDirectory(); + + if (!run_dir) { + VIR_ERROR(_("Can't determine user directory")); + goto cleanup; + } + } + if (!run_dir) { + virReportOOMError(); + 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_LOCK_DAEMON_ERR_RUNDIR; + goto cleanup; + } + umask(old_umask); + + /* If we have a pidfile set, claim it now, exiting if already taken */ + if ((pid_file_fd = virPidFileAcquirePath(pid_file, getpid())) < 0) { + ret = VIR_LOCK_DAEMON_ERR_PIDFILE; + goto cleanup; + } + + if (!(lockDaemon = virLockDaemonNew(privileged))) { + ret = VIR_LOCK_DAEMON_ERR_INIT; + goto cleanup; + } + + if (virLockDaemonSetupNetworking(lockDaemon->srv, sock_file) < 0) { + ret = VIR_LOCK_DAEMON_ERR_NETWORK; + goto cleanup; + } + + if ((virLockDaemonSetupSignals(lockDaemon->srv)) < 0) { + ret = VIR_LOCK_DAEMON_ERR_SIGNAL; + goto cleanup; + } + + /* Disable error func, now logging is setup */ + virSetErrorFunc(NULL, virLockDaemonErrorHandler); + + + /* 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(lockDaemon->srv, true); + virNetServerRun(lockDaemon->srv); + ret = 0; + +cleanup: + virLockDaemonFree(lockDaemon); + 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(run_dir); + return ret; +} diff --git a/src/locking/lock_daemon.h b/src/locking/lock_daemon.h new file mode 100644 index 0000000..7bc8c2e --- /dev/null +++ b/src/locking/lock_daemon.h @@ -0,0 +1,43 @@ +/* + * lock_daemon.h: lock management daemon + * + * Copyright (C) 2006-2012 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; If not, see + * <http://www.gnu.org/licenses/>. + * + * Author: Daniel P. Berrange <berrange@xxxxxxxxxx> + */ + +#ifndef __VIR_LOCK_DAEMON_H__ +# define __VIR_LOCK_DAEMON_H__ + +# include "virlockspace.h" +# include "threads.h" + +typedef struct _virLockDaemon virLockDaemon; +typedef virLockDaemon *virLockDaemonPtr; + +typedef struct _virLockDaemonClient virLockDaemonClient; +typedef virLockDaemonClient *virLockDaemonClientPtr; + +struct _virLockDaemonClient { + virMutex lock; + + pid_t clientPid; +}; + +extern virLockDaemonPtr lockDaemon; + +#endif /* __VIR_LOCK_DAEMON_H__ */ diff --git a/src/locking/lock_daemon_config.c b/src/locking/lock_daemon_config.c new file mode 100644 index 0000000..c64de67 --- /dev/null +++ b/src/locking/lock_daemon_config.c @@ -0,0 +1,193 @@ +/* + * lock_daemon_config.h: virtlockd config file handling + * + * Copyright (C) 2006-2012 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 + * <http://www.gnu.org/licenses/>. + * + * Author: Daniel P. Berrange <berrange@xxxxxxxxxx> + */ + +#include <config.h> + +#include "lock_daemon_config.h" +#include "conf.h" +#include "memory.h" +#include "virterror_internal.h" +#include "logging.h" +#include "rpc/virnetserver.h" +#include "configmake.h" + +#define VIR_FROM_THIS VIR_FROM_CONF + + +/* 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, + virConfTypeName(p->type), + virConfTypeName(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 strdup succeeds, + store the result in var_name. Otherwise, (i.e. invalid type, or 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 (!(data->var_name = strdup(p->str))) { \ + virReportOOMError(); \ + goto error; \ + } \ + } \ + } while (0) + +/* Like GET_CONF_STR, but for integral values. */ +#define GET_CONF_INT(conf, filename, var_name) \ + do { \ + virConfValuePtr p = virConfGetValue(conf, #var_name); \ + if (p) { \ + if (checkType(p, filename, #var_name, VIR_CONF_LONG) < 0) \ + goto error; \ + data->var_name = p->l; \ + } \ + } while (0) + +int +virLockDaemonConfigFilePath(bool privileged, char **configfile) +{ + if (privileged) { + if (!(*configfile = strdup(SYSCONFDIR "/libvirt/virtlockd.conf"))) + goto no_memory; + } else { + char *configdir = NULL; + + if (!(configdir = virGetUserConfigDirectory())) + goto error; + + if (virAsprintf(configfile, "%s/virtlockd.conf", configdir) < 0) { + VIR_FREE(configdir); + goto no_memory; + } + VIR_FREE(configdir); + } + + return 0; + +no_memory: + virReportOOMError(); +error: + return -1; +} + + +virLockDaemonConfigPtr +virLockDaemonConfigNew(bool privileged ATTRIBUTE_UNUSED) +{ + virLockDaemonConfigPtr data; + + if (VIR_ALLOC(data) < 0) { + virReportOOMError(); + return NULL; + } + + data->log_buffer_size = 64; + + return data; +} + +void +virLockDaemonConfigFree(virLockDaemonConfigPtr data) +{ + if (!data) + return; + + VIR_FREE(data->log_filters); + VIR_FREE(data->log_outputs); + + VIR_FREE(data); +} + +static int +virLockDaemonConfigLoadOptions(virLockDaemonConfigPtr data, + const char *filename, + virConfPtr conf) +{ + GET_CONF_INT(conf, filename, log_level); + GET_CONF_STR(conf, filename, log_filters); + GET_CONF_STR(conf, filename, log_outputs); + GET_CONF_INT(conf, filename, log_buffer_size); + + return 0; + +error: + return -1; +} + + +/* Read the config file if it exists. + * Only used in the remote case, hence the name. + */ +int +virLockDaemonConfigLoadFile(virLockDaemonConfigPtr 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 = virLockDaemonConfigLoadOptions(data, filename, conf); + virConfFree(conf); + return ret; +} + +int virLockDaemonConfigLoadData(virLockDaemonConfigPtr data, + const char *filename, + const char *filedata) +{ + virConfPtr conf; + int ret; + + conf = virConfReadMem(filedata, strlen(filedata), 0); + if (!conf) + return -1; + + ret = virLockDaemonConfigLoadOptions(data, filename, conf); + virConfFree(conf); + return ret; +} diff --git a/src/locking/lock_daemon_config.h b/src/locking/lock_daemon_config.h new file mode 100644 index 0000000..8cb0e5d --- /dev/null +++ b/src/locking/lock_daemon_config.h @@ -0,0 +1,50 @@ +/* + * lock_daemon_config.h: virtlockd config file handling + * + * Copyright (C) 2006-2012 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 + * <http://www.gnu.org/licenses/>. + * + * Author: Daniel P. Berrange <berrange@xxxxxxxxxx> + */ + +#ifndef __VIR_LOCK_DAEMON_CONFIG_H__ +# define __VIR_LOCK_DAEMON_CONFIG_H__ + +# include "internal.h" + +typedef struct _virLockDaemonConfig virLockDaemonConfig; +typedef virLockDaemonConfig *virLockDaemonConfigPtr; + +struct _virLockDaemonConfig { + int log_level; + char *log_filters; + char *log_outputs; + int log_buffer_size; +}; + + +int virLockDaemonConfigFilePath(bool privileged, char **configfile); +virLockDaemonConfigPtr virLockDaemonConfigNew(bool privileged); +void virLockDaemonConfigFree(virLockDaemonConfigPtr data); +int virLockDaemonConfigLoadFile(virLockDaemonConfigPtr data, + const char *filename, + bool allow_missing); +int virLockDaemonConfigLoadData(virLockDaemonConfigPtr data, + const char *filename, + const char *filedata); + +#endif /* __LIBVIRTD_CONFIG_H__ */ diff --git a/src/locking/virtlockd.init.in b/src/locking/virtlockd.init.in new file mode 100644 index 0000000..e55cbf9 --- /dev/null +++ b/src/locking/virtlockd.init.in @@ -0,0 +1,93 @@ +#!/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: virtlockd +# Default-Start: 3 4 5 +# Short-Description: virtual machine lock manager +# Description: This is a daemon for managing locks +# on virtual machine disk images +### END INIT INFO + +# the following is chkconfig init header +# +# virtlockd: virtual machine lock manager +# +# chkconfig: 345 97 03 +# description: This is a daemon for managing locks \ +# on virtual machine disk images +# +# processname: virtlockd +# pidfile: ::localstatedir::/run/libvirt/virtlockd.pid +# + +# Source function library. +. ::sysconfdir::/rc.d/init.d/functions + +SERVICE=virtlockd +PROCESS=virtlockd +PIDFILE=::localstatedir::/run/libvirt/lockd/$SERVICE.pid + +VIRTLOCKD_ARGS= + +test -f ::sysconfdir::/sysconfig/virtlockd && . ::sysconfdir::/sysconfig/virtlockd + +RETVAL=0 + +start() { + echo -n $"Starting $SERVICE daemon: " + daemon --pidfile $PIDFILE --check $SERVICE $PROCESS --daemon $VIRTLOCKD_ARGS + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch ::localstatedir::/lock/subsys/$SERVICE +} + +stop() { + echo -n $"Stopping $SERVICE daemon: " + + killproc -p $PIDFILE $PROCESS + RETVAL=$? + echo + if [ $RETVAL -eq 0 ]; then + rm -f ::localstatedir::/lock/subsys/$SERVICE + rm -f $PIDFILE + fi +} + +restart() { + stop + start +} + +reload() { + echo -n $"Reloading $SERVICE configuration: " + + killproc -p $PIDFILE $PROCESS -HUP + 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::/lock/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/locking/virtlockd.sysconf b/src/locking/virtlockd.sysconf new file mode 100644 index 0000000..d44dc46 --- /dev/null +++ b/src/locking/virtlockd.sysconf @@ -0,0 +1,3 @@ +# +# Pass extra arguments to virtlockd +#VIRTLOCKD_ARGS= -- 1.7.11.7 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list