]> xenbits.xensource.com Git - libvirt.git/commitdiff
virhook: support hooks placed in $driver.d/
authorDmitry Nesterenko <dmitry.nesterenko@virtuozzo.com>
Tue, 23 Jun 2020 14:45:34 +0000 (17:45 +0300)
committerMichal Privoznik <mprivozn@redhat.com>
Tue, 23 Jun 2020 16:34:15 +0000 (18:34 +0200)
It is easier for management software (and subsequently
distributions) to install hook script under
/etc/libvirt/hooks/$driver.d/ and have libvirt execute them in
alphabetical order. To maintain backwards compatibility,
/etc/libvirt/hooks/$driver hook script is executed the first
followed by scripts from the $driver.d directory.

The stdio is chained between the scripts. The output of the first
script is input of the second and so on.

Signed-off-by: Dmitry Nesterenko <dmitry.nesterenko@virtuozzo.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
src/util/virhook.c

index 64ee9a2307a2cfcb08fac675a753bce55d005b5b..3e025fd3a6b09aa057c3c256c885fe1297b21600 100644 (file)
@@ -33,6 +33,7 @@
 #include "virfile.h"
 #include "configmake.h"
 #include "vircommand.h"
+#include "virstring.h"
 
 #define VIR_FROM_THIS VIR_FROM_HOOK
 
@@ -141,7 +142,11 @@ static int virHooksFound = -1;
 static int
 virHookCheck(int no, const char *driver)
 {
+    int ret;
+    DIR *dir;
+    struct dirent *entry;
     g_autofree char *path = NULL;
+    g_autofree char *dir_path = NULL;
 
     if (driver == NULL) {
         virReportError(VIR_ERR_INTERNAL_ERROR,
@@ -158,16 +163,39 @@ virHookCheck(int no, const char *driver)
 
     if (!virFileExists(path)) {
         VIR_DEBUG("No hook script %s", path);
-        return 0;
+    } else if (!virFileIsExecutable(path)) {
+        VIR_WARN("Non-executable hook script %s", path);
+    } else {
+        VIR_DEBUG("Found hook script %s", path);
+        return 1;
     }
 
-    if (!virFileIsExecutable(path)) {
-        VIR_WARN("Non-executable hook script %s", path);
+    dir_path = g_strdup_printf("%s.d", path);
+    if ((ret = virDirOpenIfExists(&dir, dir_path)) < 0)
+        return -1;
+
+    if (!ret) {
+        VIR_DEBUG("No hook script dir %s", dir_path);
         return 0;
     }
 
-    VIR_DEBUG("Found hook script %s", path);
-    return 1;
+    while ((ret = virDirRead(dir, &entry, dir_path)) > 0) {
+        g_autofree char *entry_path = g_build_filename(dir_path,
+                                                       entry->d_name,
+                                                       NULL);
+        if (!virFileIsExecutable(entry_path)) {
+            VIR_WARN("Non-executable hook script %s", entry_path);
+            continue;
+        }
+
+        VIR_DEBUG("Found hook script %s", entry_path);
+        ret = 1;
+        break;
+    }
+
+    VIR_DIR_CLOSE(dir);
+
+    return ret;
 }
 
 /*
@@ -282,12 +310,18 @@ virRunScript(const char *path,
  * @input: extra input given to the script on stdin
  * @output: optional address of variable to store malloced result buffer
  *
- * Implement a hook call, where the external script for the driver is
+ * Implement a hook call, where the external scripts for the driver are
  * called with the given information. This is a synchronous call, we wait for
  * 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.
  *
+ * The script from LIBVIRT_HOOK_DIR is executed the first, followed by scripts
+ * found under "$driver.d/" directory (sorted alphabetically. If output from
+ * the hook script is expected, then the output produced by LIBVIRT_HOOK_DIR
+ * script is fed as input to the first script from the "$driver.d/" directory
+ * and its output is fed as input to the second and so on.
+ *
  * Returns: 0 if the execution succeeded, 1 if the script was not found or
  *          invalid parameters, and -1 if script returned an error
  */
@@ -300,11 +334,16 @@ virHookCall(int driver,
             const char *input,
             char **output)
 {
+    int ret, script_ret;
+    DIR *dir;
+    struct dirent *entry;
     g_autofree char *path = NULL;
-    g_autoptr(virCommand) cmd = NULL;
+    g_autofree char *dir_path = NULL;
+    VIR_AUTOSTRINGLIST entries = NULL;
     const char *drvstr;
     const char *opstr;
     const char *subopstr;
+    size_t i, nentries;
 
     if (output)
         *output = NULL;
@@ -368,6 +407,61 @@ virHookCall(int driver,
         return -1;
     }
 
-    return virRunScript(path, id, opstr, subopstr, extra,
-                        input, output);
+    script_ret = 1;
+
+    if (virFileIsExecutable(path)) {
+        script_ret = virRunScript(path, id, opstr, subopstr, extra,
+                                  input, output);
+    }
+
+    dir_path = g_strdup_printf("%s.d", path);
+    if ((ret = virDirOpenIfExists(&dir, dir_path)) < 0)
+        return -1;
+
+    if (!ret)
+        return script_ret;
+
+    while ((ret = virDirRead(dir, &entry, dir_path)) > 0) {
+        g_autofree char *entry_path = g_build_filename(dir_path,
+                                                       entry->d_name,
+                                                       NULL);
+        if (!virFileIsExecutable(entry_path))
+            continue;
+
+        virStringListAdd(&entries, entry_path);
+    }
+
+    VIR_DIR_CLOSE(dir);
+
+    if (ret < 0)
+        return -1;
+
+    if (!entries)
+        return script_ret;
+
+    nentries = virStringListLength((const char **)entries);
+    qsort(entries, nentries, sizeof(*entries), virStringSortCompare);
+
+    for (i = 0; i < nentries; i++) {
+        int entry_ret;
+        const char *entry_input;
+        g_autofree char *entry_output = NULL;
+
+        /* Get input from previous output */
+        entry_input = (!script_ret && output &&
+                       !virStringIsEmpty(*output)) ? *output : input;
+        entry_ret = virRunScript(entries[i], id, opstr,
+                                 subopstr, extra, entry_input,
+                                 (output) ? &entry_output : NULL);
+        if (entry_ret < script_ret)
+            script_ret = entry_ret;
+
+        /* Replace output to new output from item */
+        if (!entry_ret && output && !virStringIsEmpty(entry_output)) {
+            g_free(*output);
+            *output = g_steal_pointer(&entry_output);
+        }
+    }
+
+    return script_ret;
 }