1 /*
2  * virhook.c: implementation of the synchronous hooks support
3  *
4  * Copyright (C) 2010-2014 Red Hat, Inc.
5  * Copyright (C) 2010 Daniel Veillard
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library.  If not, see
19  * <http://www.gnu.org/licenses/>.
20  */
21 
22 #include <config.h>
23 
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <unistd.h>
27 
28 #include "virerror.h"
29 #include "virhook.h"
30 #include "virutil.h"
31 #include "virlog.h"
32 #include "viralloc.h"
33 #include "virfile.h"
34 #include "configmake.h"
35 #include "vircommand.h"
36 #include "virstring.h"
37 #include "virglibutil.h"
38 
39 #define VIR_FROM_THIS VIR_FROM_HOOK
40 
41 VIR_LOG_INIT("util.hook");
42 
43 #define LIBVIRT_HOOK_DIR SYSCONFDIR "/libvirt/hooks"
44 
45 VIR_ENUM_DECL(virHookDriver);
46 VIR_ENUM_DECL(virHookDaemonOp);
47 VIR_ENUM_DECL(virHookSubop);
48 VIR_ENUM_DECL(virHookQemuOp);
49 VIR_ENUM_DECL(virHookLxcOp);
50 VIR_ENUM_DECL(virHookNetworkOp);
51 VIR_ENUM_DECL(virHookLibxlOp);
52 VIR_ENUM_DECL(virHookBhyveOp);
53 
54 VIR_ENUM_IMPL(virHookDriver,
55               VIR_HOOK_DRIVER_LAST,
56               "daemon",
57               "qemu",
58               "lxc",
59               "network",
60               "libxl",
61               "bhyve",
62 );
63 
64 VIR_ENUM_IMPL(virHookDaemonOp,
65               VIR_HOOK_DAEMON_OP_LAST,
66               "start",
67               "shutdown",
68               "reload",
69 );
70 
71 VIR_ENUM_IMPL(virHookSubop,
72               VIR_HOOK_SUBOP_LAST,
73               "-",
74               "begin",
75               "end",
76 );
77 
78 VIR_ENUM_IMPL(virHookQemuOp,
79               VIR_HOOK_QEMU_OP_LAST,
80               "start",
81               "stopped",
82               "prepare",
83               "release",
84               "migrate",
85               "started",
86               "reconnect",
87               "attach",
88               "restore",
89 );
90 
91 VIR_ENUM_IMPL(virHookLxcOp,
92               VIR_HOOK_LXC_OP_LAST,
93               "start",
94               "stopped",
95               "prepare",
96               "release",
97               "started",
98               "reconnect",
99 );
100 
101 VIR_ENUM_IMPL(virHookNetworkOp,
102               VIR_HOOK_NETWORK_OP_LAST,
103               "start",
104               "started",
105               "stopped",
106               "port-created",
107               "port-deleted",
108               "updated",
109 );
110 
111 VIR_ENUM_IMPL(virHookLibxlOp,
112               VIR_HOOK_LIBXL_OP_LAST,
113               "start",
114               "stopped",
115               "prepare",
116               "release",
117               "migrate",
118               "started",
119               "reconnect",
120 );
121 
122 VIR_ENUM_IMPL(virHookBhyveOp,
123               VIR_HOOK_BHYVE_OP_LAST,
124               "start",
125               "stopped",
126               "prepare",
127               "release",
128               "started",
129 );
130 
131 static int virHooksFound = -1;
132 
133 /**
134  * virHookCheck:
135  * @driver: the driver name "daemon", "qemu", "lxc"...
136  *
137  * Check is there is an installed hook for the given driver, if this
138  * is the case register it. Then subsequent calls to virHookCall
139  * will call the hook if found.
140  *
141  * Returns 1 if found, 0 if not found, and -1 in case of error
142  */
143 static int
virHookCheck(int no,const char * driver)144 virHookCheck(int no, const char *driver)
145 {
146     int ret;
147     g_autoptr(DIR) dir = NULL;
148     struct dirent *entry;
149     g_autofree char *path = NULL;
150     g_autofree char *dir_path = NULL;
151 
152     if (driver == NULL) {
153         virReportError(VIR_ERR_INTERNAL_ERROR,
154                        _("Invalid hook name for #%d"), no);
155         return -1;
156     }
157 
158     virBuildPath(&path, LIBVIRT_HOOK_DIR, driver);
159 
160     if (!virFileExists(path)) {
161         VIR_DEBUG("No hook script %s", path);
162     } else if (!virFileIsExecutable(path)) {
163         VIR_WARN("Non-executable hook script %s", path);
164     } else {
165         VIR_DEBUG("Found hook script %s", path);
166         return 1;
167     }
168 
169     dir_path = g_strdup_printf("%s.d", path);
170 
171     if (!virFileIsExecutable(dir_path) && errno != EISDIR) {
172         VIR_DEBUG("Hook dir %s is not accessible", dir_path);
173         return 0;
174     }
175 
176     if ((ret = virDirOpenIfExists(&dir, dir_path)) < 0)
177         return -1;
178 
179     if (!ret) {
180         VIR_DEBUG("No hook script dir %s", dir_path);
181         return 0;
182     }
183 
184     while ((ret = virDirRead(dir, &entry, dir_path)) > 0) {
185         g_autofree char *entry_path = g_build_filename(dir_path,
186                                                        entry->d_name,
187                                                        NULL);
188         if (!virFileIsExecutable(entry_path)) {
189             VIR_WARN("Non-executable hook script %s", entry_path);
190             continue;
191         }
192 
193         VIR_DEBUG("Found hook script %s", entry_path);
194         ret = 1;
195         break;
196     }
197 
198     return ret;
199 }
200 
201 /*
202  * virHookInitialize:
203  *
204  * Initialize synchronous hooks support.
205  * Check is there is an installed hook for all the drivers
206  *
207  * Returns the number of hooks found or -1 in case of failure
208  */
209 int
virHookInitialize(void)210 virHookInitialize(void)
211 {
212     size_t i;
213     int res, ret = 0;
214 
215     virHooksFound = 0;
216     for (i = 0; i < VIR_HOOK_DRIVER_LAST; i++) {
217         res = virHookCheck(i, virHookDriverTypeToString(i));
218         if (res < 0)
219             return -1;
220 
221         if (res == 1) {
222             virHooksFound |= (1 << i);
223             ret++;
224         }
225     }
226     return ret;
227 }
228 
229 /**
230  * virHookPresent:
231  * @driver: the driver number (from virHookDriver enum)
232  *
233  * Check if a hook exists for the given driver, this is needed
234  * to avoid unnecessary work if the hook is not present
235  *
236  * Returns 1 if present, 0 otherwise
237  */
238 int
virHookPresent(int driver)239 virHookPresent(int driver)
240 {
241     if ((driver < VIR_HOOK_DRIVER_DAEMON) ||
242         (driver >= VIR_HOOK_DRIVER_LAST))
243         return 0;
244     if (virHooksFound == -1)
245         return 0;
246 
247     if ((virHooksFound & (1 << driver)) == 0)
248         return 0;
249     return 1;
250 }
251 
252 /**
253  * virRunScript:
254  * @path: the script path
255  * @id: an id for the object '-' if non available for example on daemon hooks
256  * @op: the operation on the id
257  * @subop: a sub_operation, currently unused
258  * @extra: optional string information
259  * @input: extra input given to the script on stdin
260  * @output: optional address of variable to store malloced result buffer
261  *
262  * Implement a execution of script. This is a synchronous call, we wait for
263  * execution completion. If @output is non-NULL, *output is guaranteed to be
264  * allocated after successful virRunScript, and is best-effort allocated after
265  * failed virRunScript; the caller is responsible for freeing *output.
266  *
267  * Returns: 0 if the execution succeeded, -1 if script returned an error
268  */
269 static int
virRunScript(const char * path,const char * id,const char * op,const char * subop,const char * extra,const char * input,char ** output)270 virRunScript(const char *path,
271              const char *id,
272              const char *op,
273              const char *subop,
274              const char *extra,
275              const char *input,
276              char **output)
277 {
278     int ret;
279     g_autoptr(virCommand) cmd = NULL;
280 
281     VIR_DEBUG("Calling hook %s id=%s op=%s subop=%s extra=%s",
282               path, id, op, subop, extra);
283 
284     cmd = virCommandNewArgList(path, id, op, subop, extra, NULL);
285 
286     virCommandAddEnvPassCommon(cmd);
287 
288     if (input)
289         virCommandSetInputBuffer(cmd, input);
290     if (output)
291         virCommandSetOutputBuffer(cmd, output);
292 
293     ret = virCommandRun(cmd, NULL);
294     if (ret < 0) {
295         /* Convert INTERNAL_ERROR into known error.  */
296         virReportError(VIR_ERR_HOOK_SCRIPT_FAILED, "%s",
297                        virGetLastErrorMessage());
298     }
299 
300     return ret;
301 }
302 
303 /**
304  * virHookCall:
305  * @driver: the driver number (from virHookDriver enum)
306  * @id: an id for the object '-' if non available for example on daemon hooks
307  * @op: the operation on the id e.g. VIR_HOOK_QEMU_OP_START
308  * @sub_op: a sub_operation, currently unused
309  * @extra: optional string information
310  * @input: extra input given to the script on stdin
311  * @output: optional address of variable to store malloced result buffer
312  *
313  * Implement a hook call, where the external scripts for the driver are
314  * called with the given information. This is a synchronous call, we wait for
315  * execution completion. If @output is non-NULL, *output is guaranteed to be
316  * allocated after successful virHookCall, and is best-effort allocated after
317  * failed virHookCall; the caller is responsible for freeing *output.
318  *
319  * The script from LIBVIRT_HOOK_DIR is executed the first, followed by scripts
320  * found under "$driver.d/" directory (sorted alphabetically. If output from
321  * the hook script is expected, then the output produced by LIBVIRT_HOOK_DIR
322  * script is fed as input to the first script from the "$driver.d/" directory
323  * and its output is fed as input to the second and so on.
324  *
325  * Returns: 0 if the execution succeeded, 1 if the script was not found or
326  *          invalid parameters, and -1 if script returned an error
327  */
328 int
virHookCall(int driver,const char * id,int op,int sub_op,const char * extra,const char * input,char ** output)329 virHookCall(int driver,
330             const char *id,
331             int op,
332             int sub_op,
333             const char *extra,
334             const char *input,
335             char **output)
336 {
337     int ret, script_ret;
338     g_autoptr(DIR) dir = NULL;
339     struct dirent *entry;
340     g_autofree char *path = NULL;
341     g_autofree char *dir_path = NULL;
342     g_autoptr(virGSListString) entries = NULL;
343     const char *drvstr;
344     const char *opstr;
345     const char *subopstr;
346     GSList *next;
347 
348     if (output)
349         *output = NULL;
350 
351     if ((driver < VIR_HOOK_DRIVER_DAEMON) ||
352         (driver >= VIR_HOOK_DRIVER_LAST))
353         return 1;
354 
355     /*
356      * We cache the availability of the script to minimize impact at
357      * runtime if no script is defined, this is being reset on SIGHUP
358      */
359     if ((virHooksFound == -1) ||
360         ((driver == VIR_HOOK_DRIVER_DAEMON) &&
361          (op == VIR_HOOK_DAEMON_OP_RELOAD ||
362           op == VIR_HOOK_DAEMON_OP_SHUTDOWN)))
363         virHookInitialize();
364 
365     if ((virHooksFound & (1 << driver)) == 0)
366         return 1;
367 
368     drvstr = virHookDriverTypeToString(driver);
369 
370     opstr = NULL;
371     switch (driver) {
372         case VIR_HOOK_DRIVER_DAEMON:
373             opstr = virHookDaemonOpTypeToString(op);
374             break;
375         case VIR_HOOK_DRIVER_QEMU:
376             opstr = virHookQemuOpTypeToString(op);
377             break;
378         case VIR_HOOK_DRIVER_LXC:
379             opstr = virHookLxcOpTypeToString(op);
380             break;
381         case VIR_HOOK_DRIVER_LIBXL:
382             opstr = virHookLibxlOpTypeToString(op);
383             break;
384         case VIR_HOOK_DRIVER_NETWORK:
385             opstr = virHookNetworkOpTypeToString(op);
386             break;
387         case VIR_HOOK_DRIVER_BHYVE:
388             opstr = virHookBhyveOpTypeToString(op);
389             break;
390     }
391     if (opstr == NULL) {
392         virReportError(VIR_ERR_INTERNAL_ERROR,
393                        _("Hook for %s, failed to find operation #%d"),
394                        drvstr, op);
395         return 1;
396     }
397     subopstr = virHookSubopTypeToString(sub_op);
398     if (subopstr == NULL)
399         subopstr = "-";
400     if (extra == NULL)
401         extra = "-";
402 
403     virBuildPath(&path, LIBVIRT_HOOK_DIR, drvstr);
404 
405     script_ret = 1;
406 
407     if (virFileIsExecutable(path)) {
408         script_ret = virRunScript(path, id, opstr, subopstr, extra,
409                                   input, output);
410     }
411 
412     dir_path = g_strdup_printf("%s.d", path);
413 
414     if ((ret = virDirOpenIfExists(&dir, dir_path)) < 0)
415         return -1;
416 
417     if (!ret)
418         return script_ret;
419 
420     while ((ret = virDirRead(dir, &entry, dir_path)) > 0) {
421         g_autofree char *entry_path = g_build_filename(dir_path,
422                                                        entry->d_name,
423                                                        NULL);
424         if (!virFileIsExecutable(entry_path))
425             continue;
426 
427         entries = g_slist_prepend(entries, g_steal_pointer(&entry_path));
428     }
429 
430     if (ret < 0)
431         return -1;
432 
433     if (!entries)
434         return script_ret;
435 
436     entries = g_slist_sort(entries, (GCompareFunc) strcmp);
437 
438     for (next = entries; next; next = next->next) {
439         int entry_ret;
440         const char *entry_input;
441         g_autofree char *entry_output = NULL;
442         const char *filename = next->data;
443 
444         /* Get input from previous output */
445         entry_input = (!script_ret && output &&
446                        !virStringIsEmpty(*output)) ? *output : input;
447         entry_ret = virRunScript(filename, id, opstr,
448                                  subopstr, extra, entry_input,
449                                  (output) ? &entry_output : NULL);
450         if (entry_ret < script_ret)
451             script_ret = entry_ret;
452 
453         /* Replace output to new output from item */
454         if (!entry_ret && output && !virStringIsEmpty(entry_output)) {
455             g_free(*output);
456             *output = g_steal_pointer(&entry_output);
457         }
458     }
459 
460     return script_ret;
461 }
462