[PATCH v2 0/3] Support hooks placed in several files

Libvirt hook calls only one script (/etc/libvirt/hooks/<driver>) now. This is not convenient if scripts for hook are provided by many vendors. Script one vendor can replace previously installed script other vendor. These patches are changing behaviour of hooks to usual linux scheme - running all scripts from the directories /etc/libvirt/hooks/<driver>.d/ in alphabetical order. If we find script in old place we will execute it as first for backward compatibility. Dmitry Nesterenko (3): virhook: refactoring for support hooks placed in several files virhook: support hooks placed in several files virhook: changes in docs about support hooks placed in several files NEWS.rst | 6 ++ docs/hooks.html.in | 27 ++++++++ src/util/virhook.c | 164 +++++++++++++++++++++++++++++++++++++++------ 3 files changed, 175 insertions(+), 22 deletions(-) -- 2.18.4

Signed-off-by: Dmitry Nesterenko <dmitry.nesterenko@virtuozzo.com> --- src/util/virhook.c | 74 +++++++++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/src/util/virhook.c b/src/util/virhook.c index e499841f66..64ee9a2307 100644 --- a/src/util/virhook.c +++ b/src/util/virhook.c @@ -221,6 +221,57 @@ virHookPresent(int driver) return 1; } +/** + * virRunScript: + * @path: the script path + * @id: an id for the object '-' if non available for example on daemon hooks + * @op: the operation on the id + * @subop: a sub_operation, currently unused + * @extra: optional string information + * @input: extra input given to the script on stdin + * @output: optional address of variable to store malloced result buffer + * + * Implement a execution of script. This is a synchronous call, we wait for + * execution completion. If @output is non-NULL, *output is guaranteed to be + * allocated after successful virRunScript, and is best-effort allocated after + * failed virRunScript; the caller is responsible for freeing *output. + * + * Returns: 0 if the execution succeeded, -1 if script returned an error + */ +static int +virRunScript(const char *path, + const char *id, + const char *op, + const char *subop, + const char *extra, + const char *input, + char **output) +{ + int ret; + g_autoptr(virCommand) cmd = NULL; + + VIR_DEBUG("Calling hook %s id=%s op=%s subop=%s extra=%s", + path, id, op, subop, extra); + + cmd = virCommandNewArgList(path, id, op, subop, extra, NULL); + + virCommandAddEnvPassCommon(cmd); + + if (input) + virCommandSetInputBuffer(cmd, input); + if (output) + virCommandSetOutputBuffer(cmd, output); + + ret = virCommandRun(cmd, NULL); + if (ret < 0) { + /* Convert INTERNAL_ERROR into known error. */ + virReportError(VIR_ERR_HOOK_SCRIPT_FAILED, "%s", + virGetLastErrorMessage()); + } + + return ret; +} + /** * virHookCall: * @driver: the driver number (from virHookDriver enum) @@ -249,7 +300,6 @@ virHookCall(int driver, const char *input, char **output) { - int ret; g_autofree char *path = NULL; g_autoptr(virCommand) cmd = NULL; const char *drvstr; @@ -318,24 +368,6 @@ virHookCall(int driver, return -1; } - VIR_DEBUG("Calling hook opstr=%s subopstr=%s extra=%s", - opstr, subopstr, extra); - - cmd = virCommandNewArgList(path, id, opstr, subopstr, extra, NULL); - - virCommandAddEnvPassCommon(cmd); - - if (input) - virCommandSetInputBuffer(cmd, input); - if (output) - virCommandSetOutputBuffer(cmd, output); - - ret = virCommandRun(cmd, NULL); - if (ret < 0) { - /* Convert INTERNAL_ERROR into known error. */ - virReportError(VIR_ERR_HOOK_SCRIPT_FAILED, "%s", - virGetLastErrorMessage()); - } - - return ret; + return virRunScript(path, id, opstr, subopstr, extra, + input, output); } -- 2.18.4

Signed-off-by: Dmitry Nesterenko <dmitry.nesterenko@virtuozzo.com> --- src/util/virhook.c | 106 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 97 insertions(+), 9 deletions(-) diff --git a/src/util/virhook.c b/src/util/virhook.c index 64ee9a2307..cd26c8c9bd 100644 --- a/src/util/virhook.c +++ b/src/util/virhook.c @@ -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,7 +310,7 @@ 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 is * 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 @@ -300,11 +328,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 +401,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; + char *entry_input; + g_autofree char *entry_output = NULL; + + /* Get input from previous output */ + entry_input = (!script_ret && output && + !virStringIsEmpty(*output)) ? *output : (char *)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; } -- 2.18.4

On 6/23/20 4:45 PM, Dmitry Nesterenko wrote:
Signed-off-by: Dmitry Nesterenko <dmitry.nesterenko@virtuozzo.com> --- src/util/virhook.c | 106 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 97 insertions(+), 9 deletions(-)
diff --git a/src/util/virhook.c b/src/util/virhook.c index 64ee9a2307..cd26c8c9bd 100644 --- a/src/util/virhook.c +++ b/src/util/virhook.c @@ -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,7 +310,7 @@ 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 is
s/is/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
I'm documenting the $driver.d/ directory and the output chaining.
@@ -300,11 +328,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 +401,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; + char *entry_input; + g_autofree char *entry_output = NULL; + + /* Get input from previous output */ + entry_input = (!script_ret && output && + !virStringIsEmpty(*output)) ? *output : (char *)input;
How about making entry_input const char* instead of typecast?
+ 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; }
Michal

Signed-off-by: Dmitry Nesterenko <dmitry.nesterenko@virtuozzo.com> --- NEWS.rst | 6 ++++++ docs/hooks.html.in | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index 42d159b233..18917d9e95 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -28,6 +28,12 @@ v6.5.0 (unreleased) schema for node devices was expanded to support attributes for mediated devices. + * virhook: Support hooks placed in several files + + Running all scripts from directory /etc/libvirt/hooks/<driver>.d in + alphabetical order. Hook script in old place will be executed + as first for backward compatibility. + * **Improvements** * **Bug fixes** diff --git a/docs/hooks.html.in b/docs/hooks.html.in index 7c9d3ef7f3..a38ba05522 100644 --- a/docs/hooks.html.in +++ b/docs/hooks.html.in @@ -36,6 +36,9 @@ <li>If your installation of libvirt has instead been compiled from source, it is likely to be <code>/usr/local/etc/libvirt/hooks/</code>.</li> + <li><span class="since">Since 6.5.0</span>, you can also place several + hook scripts in the directories + <code>/etc/libvirt/hooks/<driver>.d/</code>.</li> </ul> <p>To use hook scripts, you will need to create this <code>hooks</code> directory manually, place the desired hook scripts inside, then make @@ -59,6 +62,10 @@ Executed when a network is started or stopped or an interface is plugged/unplugged to/from the network</li> </ul> + <p><span class="since">Since 6.5.0</span>, you can also have + several scripts with any name in the directories + <code>/etc/libvirt/hooks/<driver>.d/</code>. They are + executed in alphabetical order after main script.</p> <br/> <h2><a id="structure">Script structure</a></h2> @@ -191,6 +198,16 @@ script returns failure or the output XML is not valid, restore of the image will be aborted. This hook may be used, e.g., to change location of disk images for restored domains.</li> + <li><span class="since">Since 6.5.0</span>, you can also place several + hook scripts in the directory + <code>/etc/libvirt/hooks/qemu.d/</code>. They are executed in + alphabetical order after main script. In this case each script also + acts as filter and can modify the domain XML and print it out on + its standart output. This script output is passed to standard input + next script in order. Empty output from any script is also identical + to copying the input XML without changing it. + In case any script returns failure common process will be aborted, + but all scripts from the directory will are executed.</li> <li><span class="since">Since 0.9.13</span>, the qemu hook script is also called when the libvirtd daemon restarts and reconnects to previously running QEMU processes. If the script fails, the @@ -274,6 +291,16 @@ script returns failure or the output XML is not valid, incoming migration will be canceled. This hook may be used, e.g., to change location of disk images for incoming domains.</li> + <li><span class="since">Since 6.5.0</span>, you can also place several + hook scripts in the directory + <code>/etc/libvirt/hooks/libxl.d/</code>. They are executed in + alphabetical order after main script. In this case each script also + acts as filter and can modify the domain XML and print it out on + its standart output. This script output is passed to standard input + next script in order. Empty output from any script is also identical + to copying the input XML without changing it. + In case any script returns failure common process will be aborted, + but all scripts from the directory will are executed.</li> <li><span class="since">Since 2.1.0</span>, the libxl hook script is also called when the libvirtd daemon restarts and reconnects to previously running Xen domains. If the script fails, the -- 2.18.4

On 6/23/20 4:45 PM, Dmitry Nesterenko wrote:
Signed-off-by: Dmitry Nesterenko <dmitry.nesterenko@virtuozzo.com> --- NEWS.rst | 6 ++++++ docs/hooks.html.in | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+)
This should have been split into two patches: one changing the NEWS.rst and the rest. The thing is - when backporting patches from master, NEWS.rst would create unnecessary conflicts. I'm splitting it locally. Michal

On 6/23/20 4:45 PM, Dmitry Nesterenko wrote:
Libvirt hook calls only one script (/etc/libvirt/hooks/<driver>) now. This is not convenient if scripts for hook are provided by many vendors. Script one vendor can replace previously installed script other vendor. These patches are changing behaviour of hooks to usual linux scheme - running all scripts from the directories /etc/libvirt/hooks/<driver>.d/ in alphabetical order. If we find script in old place we will execute it as first for backward compatibility.
Dmitry Nesterenko (3): virhook: refactoring for support hooks placed in several files virhook: support hooks placed in several files virhook: changes in docs about support hooks placed in several files
NEWS.rst | 6 ++ docs/hooks.html.in | 27 ++++++++ src/util/virhook.c | 164 +++++++++++++++++++++++++++++++++++++++------ 3 files changed, 175 insertions(+), 22 deletions(-)
I'm fixing all the small nits, and pushing. Reviewed-by: Michal Privoznik <mprivozn@redhat.com> Congratulations on your first libvirt contribution! Michal
participants (2)
-
Dmitry Nesterenko
-
Michal Privoznik