Add hook utilities

This exports 3 basic routines:
  - virHookInitialize() initializing the hook support by looking for
    scripts availability
  - virHookPresent() used to test if there is a hook for a given driver
  - virHookCall() which actually calls a synchronous script hook with
    the needed parameters
Note that this doesn't expose any public API except for the locations
and arguments passed to the scripts

* src/Makefile.am: add the 2 new files
* src/util/hooks.h src/util/hooks.c: implements the 3 functions
* src/libvirt_private.syms: export the 3 symbols internally

diff --git a/src/Makefile.am b/src/Makefile.am
index 5f6b325..8f1e549 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -54,6 +54,7 @@ UTIL_SOURCES =                                                
        \
                util/cgroup.c util/cgroup.h                     \
                util/event.c util/event.h                       \
                util/hash.c util/hash.h                         \
+               util/hooks.c util/hooks.h                       \
                util/iptables.c util/iptables.h                 \
                util/ebtables.c util/ebtables.h                 \
                util/json.c util/json.h                         \
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index d9aff54..4e4f126 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -242,6 +242,12 @@ virHashSearch;
 virHashSize;
 
 
+# hooks.h
+virHookCall;
+virHookInitialize;
+virHookPresent;
+
+
 # interface_conf.h
 virInterfaceDefFormat;
 virInterfaceDefParseFile;
diff --git a/src/util/hooks.c b/src/util/hooks.c
new file mode 100644
index 0000000..3c027d6
--- /dev/null
+++ b/src/util/hooks.c
@@ -0,0 +1,439 @@
+/*
+ * hooks.c: implementation of the synchronous hooks support
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ * Copyright (C) 2010 Daniel Veillard
+ *
+ * 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 Veillard <veill...@redhat.com>
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "virterror_internal.h"
+#include "hooks.h"
+#include "util.h"
+#include "conf/domain_conf.h"
+#include "logging.h"
+#include "memory.h"
+
+#define VIR_FROM_THIS VIR_FROM_HOOK
+
+#define virHookReportError(code, ...)                              \
+    virReportErrorHelper(NULL, VIR_FROM_HOOK, code, __FILE__,      \
+                         __FUNCTION__, __LINE__, __VA_ARGS__)
+
+#define LIBVIRT_HOOK_DIR SYSCONF_DIR "/libvirt/hooks"
+
+VIR_ENUM_DECL(virHookDriver)
+VIR_ENUM_DECL(virHookDaemonOp)
+VIR_ENUM_DECL(virHookSubop)
+VIR_ENUM_DECL(virHookQemuOp)
+VIR_ENUM_DECL(virHookLxcOp)
+
+VIR_ENUM_IMPL(virHookDriver,
+              VIR_HOOK_DRIVER_LAST,
+              "daemon",
+              "qemu",
+              "lxc")
+
+VIR_ENUM_IMPL(virHookDaemonOp, VIR_HOOK_DAEMON_OP_LAST,
+              "start",
+              "shutdown",
+              "reload")
+
+VIR_ENUM_IMPL(virHookSubop, VIR_HOOK_SUBOP_LAST,
+              "-",
+              "begin",
+              "end")
+
+VIR_ENUM_IMPL(virHookQemuOp, VIR_HOOK_QEMU_OP_LAST,
+              "start",
+              "stopped")
+
+VIR_ENUM_IMPL(virHookLxcOp, VIR_HOOK_QEMU_OP_LAST,
+              "start",
+              "stopped")
+
+static int virHooksFound = -1;
+
+/**
+ * virHookCheck:
+ * @driver: the driver name "daemon", "qemu", "lxc"...
+ *
+ * Check is there is an installed hook for the given driver, if this
+ * is the case register it. Then subsequent calls to virHookCall
+ * will call the hook if found.
+ *
+ * Returns 1 if found, 0 if not found, and -1 in case of error
+ */
+static int
+virHookCheck(int no, const char *driver) {
+    char *path;
+    struct stat sb;
+    int ret;
+
+    if (driver == NULL) {
+        virHookReportError(VIR_ERR_INTERNAL_ERROR,
+                   _("Invalid hook name for #%d"), no);
+        return(-1);
+    }
+
+    ret = virBuildPath(&path, LIBVIRT_HOOK_DIR, driver);
+    if ((ret < 0) || (path == NULL)) {
+        virHookReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Failed to build path for %s hook"),
+                           driver);
+        return(-1);
+    }
+
+    if (stat(path, &sb) < 0) {
+        ret = 0;
+        VIR_DEBUG("No hook script %s", path);
+    } else {
+        if (access(path, X_OK) != 0) {
+            ret = 0;
+            VIR_WARN("Non executable hook script %s", path);
+        } else {
+            ret = 1;
+            VIR_DEBUG("Found hook script %s", path);
+        }
+    }
+
+    VIR_FREE(path);
+    return(ret);
+}
+
+/*
+ * virHookInitialize:
+ *
+ * Initialize syncronous hooks support.
+ * Check is there is an installed hook for all the drivers
+ *
+ * Returns the number of hooks found or -1 in case of failure
+ */
+int
+virHookInitialize(void) {
+    int i, res, ret = 0;
+
+    virHooksFound = 0;
+    for (i = 0;i < VIR_HOOK_DRIVER_LAST;i++) {
+        res = virHookCheck(i, virHookDriverTypeToString(i));
+        if (res < 0)
+            return(-1);
+
+        if (res == 1) {
+            virHooksFound |= (1 << i);
+            ret++;
+        }
+    }
+    return(ret);
+}
+
+/**
+ * virHookPresent:
+ * @driver: the driver number (from virHookDriver enum)
+ *
+ * Check if a hook exists for the given driver, this is needed
+ * to avoid unecessary work if the hook is not present
+ *
+ * Returns 1 if present, 0 otherwise
+ */
+int
+virHookPresent(int driver) {
+    if ((driver < VIR_HOOK_DRIVER_DAEMON) ||
+        (driver >= VIR_HOOK_DRIVER_LAST))
+        return(0);
+    if (virHooksFound == -1)
+        return(0);
+
+    if ((virHooksFound & (1 << driver)) == 0)
+        return(0);
+    return(1);
+}
+
+/*
+ * virHookCall:
+ * @driver: the driver number (from virHookDriver enum)
+ * @id: an id for the object '-' if non available for example on daemon hooks
+ * @op: the operation on the id e.g. VIR_HOOK_QEMU_OP_START
+ * @sub_op: a sub_operation, currently unused
+ * @extra: optional string informations
+ * @input: extra input given to the script on stdin
+ *
+ * Implement an Hook call, where the external script for the driver is
+ * called with the given informations. This is a synchronous call, we wait for
+ * execution completion
+ *
+ * Returns: 0 if the execution suceeded, 1 if the script was not found or
+ *          invalid parameters, and -1 if script returned an error
+ */
+int
+virHookCall(int driver, const char *id, int op, int sub_op, const char *extra,
+            const char *input) {
+    int ret, waitret, exitstatus, i;
+    char *path;
+    int argc = 0, arga = 0;
+    const char **argv = NULL;
+    int envc = 0, enva = 0;
+    const char **env = NULL;
+    const char *drvstr;
+    const char *opstr;
+    const char *subopstr;
+    pid_t pid;
+    int outfd = -1, errfd = -1;
+    int pipefd[2] = { -1, -1};
+    char *outbuf = NULL;
+    char *errbuf = NULL;
+
+    if ((driver < VIR_HOOK_DRIVER_DAEMON) ||
+        (driver >= VIR_HOOK_DRIVER_LAST))
+        return(1);
+
+    /*
+     * We cache the availability of the script to minimize impact at
+     * runtime if no script is defined, this is being reset on SIGUP
+     */
+    if ((virHooksFound == -1) ||
+        ((driver == VIR_HOOK_DRIVER_DAEMON) &&
+         (op == VIR_HOOK_DAEMON_OP_RELOAD)))
+        virHookInitialize();
+
+    if ((virHooksFound & (1 << driver)) == 0)
+        return(1);
+
+    drvstr = virHookDriverTypeToString(driver);
+
+    opstr = NULL;
+    switch (driver) {
+        case VIR_HOOK_DRIVER_DAEMON:
+            opstr = virHookDaemonOpTypeToString(op);
+            break;
+        case VIR_HOOK_DRIVER_QEMU:
+            opstr = virHookQemuOpTypeToString(op);
+            break;
+        case VIR_HOOK_DRIVER_LXC:
+            opstr = virHookLxcOpTypeToString(op);
+            break;
+    }
+    if (opstr == NULL) {
+        virHookReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Hook for %s, failed to find operation #%d"),
+                           drvstr, op);
+        return(1);
+    }
+    subopstr = virHookSubopTypeToString(sub_op);
+    if (subopstr == NULL)
+        subopstr = "-";
+    if (extra == NULL)
+        extra = "-";
+
+    ret = virBuildPath(&path, LIBVIRT_HOOK_DIR, drvstr);
+    if ((ret < 0) || (path == NULL)) {
+        virHookReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Failed to build path for %s hook"),
+                           drvstr);
+        return(-1);
+    }
+
+    /*
+     * Convenience macros borrowed from qemudBuildCommandLine()
+     */
+#define ADD_ARG_SPACE                                                   \
+    do {                                                                \
+        if (argc == arga) {                                             \
+            arga += 10;                                                 \
+            if (VIR_REALLOC_N(argv, arga) < 0)                          \
+                goto no_memory;                                         \
+        }                                                               \
+    } while (0)
+
+#define ADD_ARG(thisarg)                                                \
+    do {                                                                \
+        ADD_ARG_SPACE;                                                  \
+        argv[argc++] = thisarg;                                         \
+    } while (0)
+
+#define ADD_ARG_LIT(thisarg)                                            \
+    do {                                                                \
+        ADD_ARG_SPACE;                                                  \
+        if ((argv[argc++] = strdup(thisarg)) == NULL)                   \
+            goto no_memory;                                             \
+    } while (0)
+
+#define ADD_ENV_SPACE                                                   \
+    do {                                                                \
+        if (envc == enva) {                                             \
+            enva += 10;                                                 \
+            if (VIR_REALLOC_N(env, enva) < 0)                           \
+                goto no_memory;                                         \
+        }                                                               \
+    } while (0)
+
+#define ADD_ENV(thisarg)                                                \
+    do {                                                                \
+        ADD_ENV_SPACE;                                                  \
+        env[envc++] = thisarg;                                          \
+    } while (0)
+
+#define ADD_ENV_LIT(thisarg)                                            \
+    do {                                                                \
+        ADD_ENV_SPACE;                                                  \
+        if ((env[envc++] = strdup(thisarg)) == NULL)                    \
+            goto no_memory;                                             \
+    } while (0)
+
+#define ADD_ENV_PAIR(envname, val)                                      \
+    do {                                                                \
+        char *envval;                                                   \
+        ADD_ENV_SPACE;                                                  \
+        if (virAsprintf(&envval, "%s=%s", envname, val) < 0)            \
+            goto no_memory;                                             \
+        env[envc++] = envval;                                           \
+    } while (0)
+
+#define ADD_ENV_COPY(envname)                                           \
+    do {                                                                \
+        char *val = getenv(envname);                                    \
+        if (val != NULL) {                                              \
+            ADD_ENV_PAIR(envname, val);                                 \
+        }                                                               \
+    } while (0)
+
+    ADD_ENV_LIT("LC_ALL=C");
+
+    ADD_ENV_COPY("LD_PRELOAD");
+    ADD_ENV_COPY("LD_LIBRARY_PATH");
+    ADD_ENV_COPY("PATH");
+    ADD_ENV_COPY("HOME");
+    ADD_ENV_COPY("USER");
+    ADD_ENV_COPY("LOGNAME");
+    ADD_ENV_COPY("TMPDIR");
+    ADD_ENV(NULL);
+
+    ADD_ARG_LIT(path);
+    ADD_ARG_LIT(id);
+    ADD_ARG_LIT(opstr);
+    ADD_ARG_LIT(subopstr);
+
+    ADD_ARG_LIT(extra);
+    ADD_ARG(NULL);
+
+    /* pass any optional input on the script stdin */
+    if (input != NULL) {
+        if (pipe(pipefd) < -1) {
+            virReportSystemError(errno, "%s",
+                             _("unable to create pipe for hook input"));
+            ret = 1;
+            goto cleanup;
+        }
+        if (safewrite(pipefd[1], input, strlen(input)) < 0) {
+            virReportSystemError(errno, "%s",
+                             _("unable to write to pipe for hook input"));
+            ret = 1;
+            goto cleanup;
+        }
+        ret = virExec(argv, env, NULL, &pid, pipefd[0], &outfd, &errfd,
+                      VIR_EXEC_NONE | VIR_EXEC_NONBLOCK);
+        close(pipefd[1]);
+        pipefd[1] = -1;
+    } else {
+        ret = virExec(argv, env, NULL, &pid, -1, &outfd, &errfd,
+                      VIR_EXEC_NONE | VIR_EXEC_NONBLOCK);
+    }
+    if (ret < 0) {
+        virHookReportError(VIR_ERR_HOOK_SCRIPT_FAILED,
+                           _("Failed to execute %s hook script"),
+                           path);
+        ret = 1;
+        goto cleanup;
+    }
+
+    /*
+     * we are interested in the error log if any and make sure the
+     * script doesn't block on stdout/stderr descriptors being full
+     * stdout can be useful for debug too.
+     */
+    if (virPipeReadUntilEOF(outfd, errfd, &outbuf, &errbuf) < 0) {
+        virReportSystemError(errno, _("cannot wait for '%s'"), path);
+        while (waitpid(pid, &exitstatus, 0) == -1 && errno == EINTR)
+            ;
+        ret = 1;
+        goto cleanup;
+    }
+
+    if (outbuf)
+        VIR_DEBUG("Command stdout: %s", outbuf);
+    if (errbuf)
+        VIR_DEBUG("Command stderr: %s", errbuf);
+
+    while ((waitret = waitpid(pid, &exitstatus, 0) == -1) &&
+           (errno == EINTR));
+    if (waitret == -1) {
+        virReportSystemError(errno, _("Failed to wait for '%s'"), path);
+        ret = 1;
+        goto cleanup;
+    }
+    if (exitstatus != 0) {
+        virHookReportError(VIR_ERR_HOOK_SCRIPT_FAILED,
+                           _("Hook script %s %s failed with error code %d:%s"),
+                           path, drvstr, exitstatus, errbuf);
+        ret = -1;
+    }
+
+cleanup:
+    if (pipefd[0] > 0)
+        close(pipefd[0]);
+    if (pipefd[1] > 0)
+        close(pipefd[1]);
+    if (argv) {
+        for (i = 0 ; i < argc ; i++)
+            VIR_FREE((argv)[i]);
+        VIR_FREE(argv);
+    }
+    if (env) {
+        for (i = 0 ; i < envc ; i++)
+            VIR_FREE((env)[i]);
+        VIR_FREE(env);
+    }
+    VIR_FREE(outbuf);
+    VIR_FREE(errbuf);
+    VIR_FREE(path);
+
+    return(ret);
+
+no_memory:
+    virReportOOMError();
+
+    goto cleanup;
+
+#undef ADD_ARG
+#undef ADD_ARG_LIT
+#undef ADD_ARG_SPACE
+#undef ADD_USBDISK
+#undef ADD_ENV
+#undef ADD_ENV_COPY
+#undef ADD_ENV_LIT
+#undef ADD_ENV_SPACE
+}
+
diff --git a/src/util/hooks.h b/src/util/hooks.h
new file mode 100644
index 0000000..28fb17e
--- /dev/null
+++ b/src/util/hooks.h
@@ -0,0 +1,77 @@
+/*
+ * hook.h: internal entry points needed for synchronous hooks support
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ * Copyright (C) 2010 Daniel Veillard
+ *
+ * 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 Veillard <veill...@redhat.com>
+ */
+
+#ifndef __VIR_HOOKS_H__
+# define __VIR_HOOKS_H__
+
+# include "internal.h"
+# include "util.h"
+
+enum virHookDriverType {
+    VIR_HOOK_DRIVER_DAEMON = 0,        /* Daemon related events */
+    VIR_HOOK_DRIVER_QEMU,              /* QEmu domains related events */
+    VIR_HOOK_DRIVER_LXC,               /* LXC domains related events */
+
+    VIR_HOOK_DRIVER_LAST,
+};
+
+enum virHookDaemonOpType {
+    VIR_HOOK_DAEMON_OP_START,          /* daemon is about to start */
+    VIR_HOOK_DAEMON_OP_SHUTDOWN,       /* daemon is about to shutdown */
+    VIR_HOOK_DAEMON_OP_RELOAD,         /* driver reload with SIGHUP */
+
+    VIR_HOOK_DAEMON_OP_LAST,
+};
+
+enum virHookSubopType {
+    VIR_HOOK_SUBOP_NONE,               /* no sub-operation */
+    VIR_HOOK_SUBOP_BEGIN,              /* beginning of the operation */
+    VIR_HOOK_SUBOP_END,                /* end of the operation */
+
+    VIR_HOOK_SUBOP_LAST,
+};
+
+enum virHookQemuOpType {
+    VIR_HOOK_QEMU_OP_START,            /* domain is about to start */
+    VIR_HOOK_QEMU_OP_STOPPED,          /* domain has stopped */
+
+    VIR_HOOK_QEMU_OP_LAST,
+};
+
+enum virHookLxcOpType {
+    VIR_HOOK_LXC_OP_START,            /* domain is about to start */
+    VIR_HOOK_LXC_OP_STOPPED,          /* domain has stopped */
+
+    VIR_HOOK_LXC_OP_LAST,
+};
+
+int virHookInitialize(void);
+
+int virHookPresent(int driver);
+
+int virHookCall(int driver, const char *id, int op, int sub_op,
+                const char *extra, const char *input);
+
+#endif /* __VIR_HOOKS_H__ */
+
+

-- 
Daniel Veillard      | libxml Gnome XML XSLT toolkit  http://xmlsoft.org/
dan...@veillard.com  | Rpmfind RPM search engine http://rpmfind.net/
http://veillard.com/ | virtualization library  http://libvirt.org/

--
libvir-list mailing list
libvir-list@redhat.com
https://www.redhat.com/mailman/listinfo/libvir-list

Reply via email to