diff --git a/po/POTFILES.in b/po/POTFILES.in index d24cc1b0ffa983b0122a409a22c48eb7d4b141ae..645c344d3d93030128319a39843bac7024b60733 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -63,6 +63,7 @@ src/uml/uml_driver.c src/util/authhelper.c src/util/bridge.c src/util/conf.c +src/util/hooks.c src/util/hostusb.c src/util/json.c src/util/logging.c diff --git a/src/Makefile.am b/src/Makefile.am index 2409a57354a5795384d37b66b54fa16a8b8bf546..74ee6c740e542058de97e9f6dc5d441b9b24e1de 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 779898b170d732ab19fc4dc2008401d357893037..cbe7b6f1bef37764ab9feccf265c192277ec2815 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -263,6 +263,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 0000000000000000000000000000000000000000..755679d73dd411bb4c1d39fe09baf4b2f8acb087 --- /dev/null +++ b/src/util/hooks.c @@ -0,0 +1,449 @@ +/* + * 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 + */ + +#include + +#include +#include +#include +#include +#include +#include + +#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) || (!S_ISREG(sb.st_mode))) { + 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 synchronous 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 unnecessary 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 information + * @input: extra input given to the script on stdin + * + * Implement a hook call, where the external script for the driver is + * called with the given information. This is a synchronous call, we wait for + * execution completion + * + * Returns: 0 if the execution succeeded, 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 SIGHUP + */ + 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); + if (close(pipefd[1]) < 0) { + virReportSystemError(errno, "%s", + _("unable to close pipe for hook input")); + } + 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) { + if (close(pipefd[0]) < 0) { + virReportSystemError(errno, "%s", + _("unable to close pipe for hook input")); + } + } + if (pipefd[1] >= 0) { + if (close(pipefd[1]) < 0) { + virReportSystemError(errno, "%s", + _("unable to close pipe for hook input")); + } + } + 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 0000000000000000000000000000000000000000..f311ed1022f14a7083e06a6a68b162eb7aef2bcf --- /dev/null +++ b/src/util/hooks.h @@ -0,0 +1,75 @@ +/* + * 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 + */ + +#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__ */