virhook.c 7.8 KB
Newer Older
D
Daniel Veillard 已提交
1
/*
2
 * virhook.c: implementation of the synchronous hooks support
D
Daniel Veillard 已提交
3
 *
4
 * Copyright (C) 2010-2013 Red Hat, Inc.
D
Daniel Veillard 已提交
5 6 7 8 9 10 11 12 13 14 15 16 17
 * 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
18
 * License along with this library.  If not, see
O
Osier Yang 已提交
19
 * <http://www.gnu.org/licenses/>.
D
Daniel Veillard 已提交
20 21 22 23 24 25 26
 *
 * Author: Daniel Veillard <veillard@redhat.com>
 */

#include <config.h>

#include <sys/types.h>
E
Eric Blake 已提交
27
#include <sys/wait.h>
D
Daniel Veillard 已提交
28 29 30 31 32
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

33
#include "virerror.h"
34
#include "virhook.h"
35
#include "virutil.h"
36
#include "virlog.h"
37
#include "viralloc.h"
E
Eric Blake 已提交
38
#include "virfile.h"
39
#include "configmake.h"
40
#include "vircommand.h"
D
Daniel Veillard 已提交
41 42 43

#define VIR_FROM_THIS VIR_FROM_HOOK

44
#define LIBVIRT_HOOK_DIR SYSCONFDIR "/libvirt/hooks"
D
Daniel Veillard 已提交
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69

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",
70 71
              "stopped",
              "prepare",
J
Jiri Denemark 已提交
72
              "release",
73 74 75 76
              "migrate",
              "started",
              "reconnect",
              "attach")
D
Daniel Veillard 已提交
77

P
Paolo Smiraglia 已提交
78
VIR_ENUM_IMPL(virHookLxcOp, VIR_HOOK_LXC_OP_LAST,
D
Daniel Veillard 已提交
79
              "start",
80 81 82 83 84
              "stopped",
              "prepare",
              "release",
              "started",
              "reconnect")
D
Daniel Veillard 已提交
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103

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;
    int ret;

    if (driver == NULL) {
104 105
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Invalid hook name for #%d"), no);
E
Eric Blake 已提交
106
        return -1;
D
Daniel Veillard 已提交
107 108 109 110
    }

    ret = virBuildPath(&path, LIBVIRT_HOOK_DIR, driver);
    if ((ret < 0) || (path == NULL)) {
111 112 113
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Failed to build path for %s hook"),
                       driver);
E
Eric Blake 已提交
114
        return -1;
D
Daniel Veillard 已提交
115 116
    }

117
    if (!virFileExists(path)) {
D
Daniel Veillard 已提交
118
        ret = 0;
119 120 121 122
        VIR_DEBUG("No hook script %s", path);
    } else if (!virFileIsExecutable(path)) {
        ret = 0;
        VIR_WARN("Non-executable hook script %s", path);
D
Daniel Veillard 已提交
123
    } else {
E
Eric Blake 已提交
124 125
        ret = 1;
        VIR_DEBUG("Found hook script %s", path);
D
Daniel Veillard 已提交
126 127 128
    }

    VIR_FREE(path);
E
Eric Blake 已提交
129
    return ret;
D
Daniel Veillard 已提交
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
}

/*
 * 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)
148
            return -1;
D
Daniel Veillard 已提交
149 150 151 152 153 154

        if (res == 1) {
            virHooksFound |= (1 << i);
            ret++;
        }
    }
155
    return ret;
D
Daniel Veillard 已提交
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
}

/**
 * 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))
171
        return 0;
D
Daniel Veillard 已提交
172
    if (virHooksFound == -1)
173
        return 0;
D
Daniel Veillard 已提交
174 175

    if ((virHooksFound & (1 << driver)) == 0)
176 177
        return 0;
    return 1;
D
Daniel Veillard 已提交
178 179
}

180
/**
D
Daniel Veillard 已提交
181 182 183 184 185 186 187
 * 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
188
 * @output: optional address of variable to store malloced result buffer
D
Daniel Veillard 已提交
189 190 191
 *
 * 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
192 193 194
 * execution completion. If @output is non-NULL, *output is guaranteed to be
 * allocated after successful virHookCall, and is best-effort allocated after
 * failed virHookCall; the caller is responsible for freeing *output.
D
Daniel Veillard 已提交
195 196 197 198 199
 *
 * Returns: 0 if the execution succeeded, 1 if the script was not found or
 *          invalid parameters, and -1 if script returned an error
 */
int
200 201 202 203 204 205 206 207
virHookCall(int driver,
            const char *id,
            int op,
            int sub_op,
            const char *extra,
            const char *input,
            char **output)
{
208
    int ret;
D
Daniel Veillard 已提交
209
    char *path;
210
    virCommandPtr cmd;
D
Daniel Veillard 已提交
211 212 213 214
    const char *drvstr;
    const char *opstr;
    const char *subopstr;

215 216 217
    if (output)
        *output = NULL;

D
Daniel Veillard 已提交
218 219
    if ((driver < VIR_HOOK_DRIVER_DAEMON) ||
        (driver >= VIR_HOOK_DRIVER_LAST))
220
        return 1;
D
Daniel Veillard 已提交
221 222 223 224 225 226 227

    /*
     * 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) &&
228 229
         (op == VIR_HOOK_DAEMON_OP_RELOAD ||
         op == VIR_HOOK_DAEMON_OP_SHUTDOWN)))
D
Daniel Veillard 已提交
230 231 232
        virHookInitialize();

    if ((virHooksFound & (1 << driver)) == 0)
233
        return 1;
D
Daniel Veillard 已提交
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249

    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) {
250 251 252
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Hook for %s, failed to find operation #%d"),
                       drvstr, op);
253
        return 1;
D
Daniel Veillard 已提交
254 255 256 257 258 259 260 261 262
    }
    subopstr = virHookSubopTypeToString(sub_op);
    if (subopstr == NULL)
        subopstr = "-";
    if (extra == NULL)
        extra = "-";

    ret = virBuildPath(&path, LIBVIRT_HOOK_DIR, drvstr);
    if ((ret < 0) || (path == NULL)) {
263 264 265
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Failed to build path for %s hook"),
                       drvstr);
266
        return -1;
D
Daniel Veillard 已提交
267 268
    }

269 270 271
    VIR_DEBUG("Calling hook opstr=%s subopstr=%s extra=%s",
              opstr, subopstr, extra);

272
    cmd = virCommandNewArgList(path, id, opstr, subopstr, extra, NULL);
D
Daniel Veillard 已提交
273

274
    virCommandAddEnvPassCommon(cmd);
D
Daniel Veillard 已提交
275

276 277
    if (input)
        virCommandSetInputBuffer(cmd, input);
278 279
    if (output)
        virCommandSetOutputBuffer(cmd, output);
D
Daniel Veillard 已提交
280

281 282 283 284 285
    ret = virCommandRun(cmd, NULL);
    if (ret < 0) {
        /* Convert INTERNAL_ERROR into known error.  */
        virErrorPtr err = virGetLastError();
        virReportError(VIR_ERR_HOOK_SCRIPT_FAILED, "%s", err->message);
286
    }
D
Daniel Veillard 已提交
287

288
    virCommandFree(cmd);
D
Daniel Veillard 已提交
289

290
    VIR_FREE(path);
D
Daniel Veillard 已提交
291

292
    return ret;
D
Daniel Veillard 已提交
293
}