[libvirt] [PATCH 8/8] Start of a lock manager daemon

Daniel P. Berrange berrange at redhat.com
Wed Dec 1 17:26:34 UTC 2010


This starts the basic framework for a lock manager daemon to
provide guarenteed isolation between VMs using disk images.
It is a simple demo of how the generic RPC server APIs are
to be used
---
 src/Makefile.am |   16 ++
 src/virtlockd.c |  620 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 636 insertions(+), 0 deletions(-)
 create mode 100644 src/virtlockd.c

diff --git a/src/Makefile.am b/src/Makefile.am
index 8986f22..4c9cc79 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1167,6 +1167,22 @@ libvirt_net_client_la_LDFLAGS = \
 libvirt_net_client_la_LIBADD = \
 			$(CYGWIN_EXTRA_LIBADD)
 
+sbin_PROGRAMS = virtlockd
+
+virtlockd_SOURCES = virtlockd.c
+virtlockd_CFLAGS = \
+			$(AM_CFLAGS)
+virtlockd_LDFLAGS = \
+			$(AM_LDFLAGS) \
+			$(CYGWIN_EXTRA_LDFLAGS) \
+			$(MINGW_EXTRA_LDFLAGS)
+virtlockd_LDADD = \
+			../gnulib/lib/libgnu.la \
+			libvirt-net-server.la \
+			libvirt-net-rpc.la \
+			libvirt_util.la \
+			$(CYGWIN_EXTRA_LIBADD)
+
 libexec_PROGRAMS =
 
 if WITH_STORAGE_DISK
diff --git a/src/virtlockd.c b/src/virtlockd.c
new file mode 100644
index 0000000..ec7dd5d
--- /dev/null
+++ b/src/virtlockd.c
@@ -0,0 +1,620 @@
+/*
+ * virtlockd.c: lock management daemon
+ *
+ * Copyright (C) 2006-2010 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ *
+ * Author: Daniel P. Berrange <berrange at redhat.com>
+ */
+
+#include <config.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <getopt.h>
+#include <stdlib.h>
+
+
+#include "util.h"
+#include "files.h"
+#include "virterror_internal.h"
+#include "logging.h"
+#include "memory.h"
+#include "conf.h"
+#include "rpc/virnetserver.h"
+#include "threads.h"
+
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+#include "configmake.h"
+
+enum {
+    VIR_DAEMON_ERR_NONE = 0,
+    VIR_DAEMON_ERR_PIDFILE,
+    VIR_DAEMON_ERR_RUNDIR,
+    VIR_DAEMON_ERR_INIT,
+    VIR_DAEMON_ERR_SIGNAL,
+    VIR_DAEMON_ERR_PRIVS,
+    VIR_DAEMON_ERR_NETWORK,
+    VIR_DAEMON_ERR_CONFIG,
+    VIR_DAEMON_ERR_HOOKS,
+
+    VIR_DAEMON_ERR_LAST
+};
+
+VIR_ENUM_DECL(virDaemonErr)
+VIR_ENUM_IMPL(virDaemonErr, VIR_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 int daemonForkIntoBackground(const char *argv0)
+{
+    int statuspipe[2];
+    if (pipe(statuspipe) < 0)
+        return -1;
+
+    int 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 daemonWritePidFile(const char *argv0, const char *pidFile) {
+    int fd;
+    FILE *fh;
+    char ebuf[1024];
+
+    if (pidFile[0] == '\0')
+        return 0;
+
+    if ((fd = open(pidFile, O_WRONLY|O_CREAT|O_EXCL, 0644)) < 0) {
+        VIR_ERROR(_("Failed to open pid file '%s' : %s"),
+                  pidFile, virStrerror(errno, ebuf, sizeof ebuf));
+        return -1;
+    }
+
+    if (!(fh = VIR_FDOPEN(fd, "w"))) {
+        VIR_ERROR(_("Failed to fdopen pid file '%s' : %s"),
+                  pidFile, virStrerror(errno, ebuf, sizeof ebuf));
+        VIR_FORCE_CLOSE(fd);
+        return -1;
+    }
+
+    if (fprintf(fh, "%lu\n", (unsigned long)getpid()) < 0) {
+        VIR_ERROR(_("%s: Failed to write to pid file '%s' : %s"),
+                  argv0, pidFile, virStrerror(errno, ebuf, sizeof ebuf));
+        VIR_FORCE_FCLOSE(fh);
+        return -1;
+    }
+
+    if (VIR_FCLOSE(fh) == EOF) {
+        VIR_ERROR(_("%s: Failed to close pid file '%s' : %s"),
+                  argv0, pidFile, virStrerror(errno, ebuf, sizeof ebuf));
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static int daemonMakePaths(char **statedir,
+                           char **pidfile,
+                           char **sockfile)
+{
+    char *userdir = NULL;
+    uid_t uid = geteuid();
+
+    if (uid == 0) {
+        if (!(*statedir = strdup(LOCALSTATEDIR "/run")))
+            goto no_memory;
+        if (!(*pidfile = strdup(LOCALSTATEDIR "/run/virtlockd.pid")))
+            goto no_memory;
+        if (!(*sockfile = strdup(LOCALSTATEDIR "/run/virtlockd.sock")))
+            goto no_memory;
+    } else {
+        if (!(userdir = virGetUserDirectory(uid)))
+            goto error;
+
+        if (virAsprintf(statedir, "%s/.libvirt", userdir) < 0)
+            goto no_memory;
+        if (virAsprintf(pidfile, "%s/.libvirt/virtlockd.pid", userdir) < 0)
+            goto no_memory;
+        if (virAsprintf(sockfile, "%s/.libvirt/virtlockd.sock", userdir) < 0)
+            goto no_memory;
+    }
+    VIR_FREE(userdir);
+    return 0;
+
+no_memory:
+    VIR_FREE(*pidfile);
+    VIR_FREE(*sockfile);
+error:
+    VIR_FREE(userdir);
+    return -1;
+}
+
+static void daemonErrorHandler(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 syslog and the logging
+ * is also saved onto the logfile libvird.log, but if verbose or error
+ * debugging is asked for then output informations or debug.
+ */
+static int
+daemonSetLogging(virConfPtr conf ATTRIBUTE_UNUSED,
+                 const char *filename ATTRIBUTE_UNUSED,
+                 int godaemon, int verbose)
+{
+    //int log_level = 0;
+    char *log_filters = NULL;
+    char *log_outputs = NULL;
+    int ret = -1;
+
+    virLogReset();
+#if 0
+    /*
+     * 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.
+     */
+    GET_CONF_INT (conf, filename, log_level);
+    if (log_level != 0)
+        virLogSetDefaultPriority(log_level);
+
+    if (virLogGetNbFilters() == 0) {
+        GET_CONF_STR (conf, filename, log_filters);
+        virLogParseFilters(log_filters);
+    }
+
+    if (virLogGetNbOutputs() == 0) {
+        GET_CONF_STR (conf, filename, log_outputs);
+        virLogParseOutputs(log_outputs);
+    }
+#endif
+
+    virLogSetFromEnv();
+
+    /*
+     * If no defined outputs, then direct to syslog when running
+     * as daemon. Otherwise the default output is stderr.
+     */
+    if (virLogGetNbOutputs() == 0) {
+        char *tmp = NULL;
+        if (godaemon) {
+            if (virAsprintf (&tmp, "%d:syslog:libvirtd",
+                             virLogGetDefaultPriority()) < 0)
+                goto free_and_fail;
+        } else {
+            if (virAsprintf (&tmp, "%d:stderr",
+                             virLogGetDefaultPriority()) < 0)
+                goto free_and_fail;
+        }
+        virLogParseOutputs(tmp);
+        VIR_FREE(tmp);
+    }
+
+    /*
+     * Command line override for --verbose
+     */
+    if ((verbose) && (virLogGetDefaultPriority() > VIR_LOG_INFO))
+        virLogSetDefaultPriority(VIR_LOG_INFO);
+
+    ret = 0;
+
+free_and_fail:
+    VIR_FREE(log_filters);
+    VIR_FREE(log_outputs);
+    return(ret);
+}
+
+/* Read the config file if it exists.
+ * Only used in the remote case, hence the name.
+ */
+static int daemonReadConfigFile(const char *filename,
+                                int godaemon, int verbose)
+{
+    virConfPtr conf;
+
+    if (!(conf = virConfReadFile (filename, 0)))
+        goto error;
+
+    if (daemonSetLogging(conf, filename, godaemon, verbose) < 0)
+        goto error;
+
+    virConfFree (conf);
+    return 0;
+
+error:
+    virConfFree (conf);
+
+    return -1;
+}
+
+/* Display version information. */
+static void daemonVersion(const char *argv0)
+{
+    printf ("%s (%s) %s\n", argv0, PACKAGE_NAME, PACKAGE_VERSION);
+}
+
+static void daemonShutdownHandler(virNetServerPtr srv,
+                                  siginfo_t *sig ATTRIBUTE_UNUSED,
+                                  void *opaque ATTRIBUTE_UNUSED)
+{
+    virNetServerQuit(srv);
+}
+
+static int daemonSetupSignals(virNetServerPtr srv)
+{
+    if (virNetServerAddSignalHandler(srv, SIGINT, daemonShutdownHandler, NULL) < 0)
+        return -1;
+    if (virNetServerAddSignalHandler(srv, SIGQUIT, daemonShutdownHandler, NULL) < 0)
+        return -1;
+    if (virNetServerAddSignalHandler(srv, SIGTERM, daemonShutdownHandler, NULL) < 0)
+        return -1;
+    return 0;
+}
+
+static int daemonSetupNetworking(virNetServerPtr srv, const char *sock_path)
+{
+    virNetServerServicePtr svc;
+
+    if (!(svc = virNetServerServiceNewUNIX(sock_path, 0700, 0, 0, false)))
+        return -1;
+
+    if (virNetServerAddService(srv, svc) < 0) {
+        virNetServerServiceFree(svc);
+        return -1;
+    }
+    return 0;
+}
+
+
+static void daemonUsage(const char *argv0)
+{
+    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\
+\n\
+  Default paths:\n\
+\n\
+    Configuration file (unless overridden by -f):\n\
+      %s/libvirt/libvirtd.conf\n\
+\n\
+    Sockets (as root):\n\
+      %s/run/virtlockd.sock\n\
+\n\
+    Sockets (as non-root):\n\
+      $HOME/.libvirt/virtlockd.sock (in UNIX abstract namespace)\n\
+\n\
+    Default PID file (as root):\
+      %s/run/virtlockd.pid\n\
+\n\
+    Default PID file (as non-root):\
+      $HOME/.libvirt/virtlockd.pid\n\
+\n"),
+             argv0,
+             SYSCONFDIR,
+             LOCALSTATEDIR,
+             LOCALSTATEDIR);
+}
+
+enum {
+    OPT_VERSION = 129
+};
+
+#define MAX_LISTEN 5
+int main(int argc, char **argv) {
+    virNetServerPtr srv = NULL;
+    const char *remote_config_file = NULL;
+    int statuswrite = -1;
+    int ret = 1;
+    int verbose = 0;
+    int godaemon = 0;
+    int timeout = 0;
+    char *state_dir = NULL;
+    char *pid_file = NULL;
+    char *sock_file = 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}
+    };
+
+    if (setlocale (LC_ALL, "") == NULL ||
+        bindtextdomain (PACKAGE, LOCALEDIR) == NULL ||
+        textdomain(PACKAGE) == NULL ||
+        virThreadInitialize() < 0 ||
+        virErrorInitialize() < 0 ||
+        virRandomInitialize(time(NULL) ^ getpid())) {
+        fprintf(stderr, _("%s: initialization failed\n"), argv[0]);
+        exit(EXIT_FAILURE);
+    }
+
+    if (daemonMakePaths(&state_dir, &pid_file, &sock_file) < 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':
+            remote_config_file = optarg;
+            break;
+
+        case OPT_VERSION:
+            daemonVersion(argv[0]);
+            return 0;
+
+        case '?':
+            daemonUsage(argv[0]);
+            return 2;
+
+        default:
+            fprintf (stderr, _("%s: internal error: unknown flag: %c\n"),
+                     argv[0], c);
+            exit (EXIT_FAILURE);
+        }
+    }
+
+    if (remote_config_file == NULL) {
+        static const char *default_config_file
+            = SYSCONFDIR "/libvirt/libvirtd.conf";
+        remote_config_file =
+            (access(default_config_file, R_OK) == 0
+             ? default_config_file
+             : "/dev/null");
+    }
+
+    if (godaemon) {
+        char ebuf[1024];
+        if ((statuswrite = daemonForkIntoBackground(argv[0])) < 0) {
+            VIR_ERROR(_("Failed to fork as daemon: %s"),
+                      virStrerror(errno, ebuf, sizeof ebuf));
+            goto cleanup;
+        }
+    }
+
+    if (!(srv = virNetServerNew(1, 1, 20))) {
+        ret = VIR_DAEMON_ERR_INIT;
+        goto cleanup;
+    }
+
+    if ((daemonSetupSignals(srv)) < 0) {
+        ret = VIR_DAEMON_ERR_SIGNAL;
+        goto cleanup;
+    }
+
+    /* If we have a pidfile set, claim it now, exiting if already taken */
+    if (daemonWritePidFile(argv[0], pid_file) < 0) {
+        pid_file = NULL; /* Prevent unlinking of someone else's pid ! */
+        ret = VIR_DAEMON_ERR_PIDFILE;
+        goto cleanup;
+    }
+
+    /* Ensure the rundir exists (on tmpfs on some systems) */
+    if (mkdir(state_dir, 0755)) {
+        if (errno != EEXIST) {
+            char ebuf[1024];
+            VIR_ERROR(_("unable to create rundir %s: %s"), state_dir,
+                      virStrerror(errno, ebuf, sizeof(ebuf)));
+            ret = VIR_DAEMON_ERR_RUNDIR;
+            goto cleanup;
+        }
+    }
+
+    /* Read the config file (if it exists). */
+    if (daemonReadConfigFile(remote_config_file, godaemon, verbose) < 0) {
+        ret = VIR_DAEMON_ERR_CONFIG;
+        goto cleanup;
+    }
+
+    if (daemonSetupNetworking(srv, sock_file) < 0) {
+        ret = VIR_DAEMON_ERR_NETWORK;
+        goto cleanup;
+    }
+
+    /* Disable error func, now logging is setup */
+    virSetErrorFunc(NULL, daemonErrorHandler);
+
+
+    /* 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(srv, true);
+    virNetServerRun(srv);
+    ret = 0;
+
+cleanup:
+    virNetServerFree(srv);
+    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)
+        unlink(pid_file);
+    virLogShutdown();
+    VIR_FREE(pid_file);
+    VIR_FREE(sock_file);
+    VIR_FREE(state_dir);
+    return ret;
+}
-- 
1.7.2.3




More information about the libvir-list mailing list