Re: [libvirt PATCH v4 12/25] nodedev: Refresh mdev devices when changes are detected

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, Feb 03, 2021 at 11:38:56AM -0600, Jonathon Jongsma wrote:
> We need to query mdevctl for changes to device definitions since an
> administrator can define new devices by executing mdevctl outside of
> libvirt.
> 
> In the future, mdevctl may add a way to signal device add/remove via
> events, but for now we resort to a bit of a workaround: monitoring the
> mdevctl config directory for changes to files. When a change is
> detected, we query mdevctl and update our device list. The mdevctl
> querying is handled in a throwaway thread, and these threads are
> synchronized with a mutex.
> 
> Signed-off-by: Jonathon Jongsma <jjongsma@xxxxxxxxxx>
> ---
>  src/node_device/node_device_udev.c | 158 +++++++++++++++++++++++++++++
>  1 file changed, 158 insertions(+)
> 
> diff --git a/src/node_device/node_device_udev.c b/src/node_device/node_device_udev.c
> index 038941ec51..8a1210cffb 100644
> --- a/src/node_device/node_device_udev.c
> +++ b/src/node_device/node_device_udev.c
> @@ -19,6 +19,7 @@
>   */
>  
>  #include <config.h>
> +#include <gio/gio.h>
>  #include <libudev.h>
>  #include <pciaccess.h>
>  #include <scsi/scsi.h>
> @@ -66,6 +67,10 @@ struct _udevEventData {
>      virCond threadCond;
>      bool threadQuit;
>      bool dataReady;
> +
> +    GList *mdevctlMonitors;
> +    virMutex mdevctlLock;
> +    int mdevctlTimeout;
>  };

Sorry for the delay in review, it took me a while to wrap my head around the
amount of GLib black magic going on in this patch.

...

> +
> +
> +static void
> +mdevctlEventHandleCallback(GFileMonitor *monitor G_GNUC_UNUSED,
> +                           GFile *file,
> +                           GFile *other_file G_GNUC_UNUSED,
> +                           GFileMonitorEvent event_type,
> +                           gpointer user_data);
> +
> +
> +/* Recursively monitors a directory and its subdirectories for file changes and
> + * returns a GList of GFileMonitor objects */
> +static GList*
> +monitorFileRecursively(udevEventDataPtr udev,
> +                       GFile *file)
> +{
> +    GList *monitors = NULL;
> +    g_autoptr(GError) error = NULL;
> +    g_autoptr(GFileEnumerator) children = NULL;
> +    GFileMonitor *mon;
> +
> +    if (!(children = g_file_enumerate_children(file, "standard::*",
> +                                               G_FILE_QUERY_INFO_NONE, NULL, &error)))
> +        goto error;
> +
> +    if (!(mon = g_file_monitor(file, G_FILE_MONITOR_NONE, NULL, &error)))
> +        goto error;
> +
> +    g_signal_connect(mon, "changed",
> +                     G_CALLBACK(mdevctlEventHandleCallback), udev);

newline here^

> +    monitors = g_list_append(monitors, mon);
> +
> +    while (true) {
> +        GFileInfo *info;
> +        GFile *child;

We should initialize all the pointers - coverity might find creative ways of
complaining about it.

> +        GList *child_monitors = NULL;
> +
> +        if (!g_file_enumerator_iterate(children, &info, &child, NULL, &error))

I hope that the fact that ^this can block will never pose a problem for us.

> +            goto error;

newline here

> +        if (!info)
> +            break;
> +
> +        if (g_file_query_file_type(child, G_FILE_QUERY_INFO_NONE, NULL) ==
> +            G_FILE_TYPE_DIRECTORY) {
> +
> +            child_monitors = monitorFileRecursively(udev, child);
> +            if (child_monitors)
> +                monitors = g_list_concat(monitors, child_monitors);
> +        }
> +    }
> +
> +    return monitors;
> +
> + error:
> +    g_list_free_full(monitors, g_object_unref);
> +    virReportError(VIR_ERR_INTERNAL_ERROR,
> +                   _("Unable to monitor directory: %s"), error->message);
> +    g_clear_error(&error);
> +    return NULL;
> +}
> +
> +
> +static void
> +mdevctlEventHandleCallback(GFileMonitor *monitor G_GNUC_UNUSED,
> +                           GFile *file,
> +                           GFile *other_file G_GNUC_UNUSED,
> +                           GFileMonitorEvent event_type,
> +                           gpointer user_data)
> +{
> +    udevEventDataPtr priv = user_data;
> +    /* if a new directory appears, monitor that directory for changes */
> +    if (event_type == G_FILE_MONITOR_EVENT_CREATED &&
> +        g_file_query_file_type(file, G_FILE_QUERY_INFO_NONE, NULL) ==
> +        G_FILE_TYPE_DIRECTORY) {
> +        GList *newmonitors = monitorFileRecursively(priv, file);
> +        priv->mdevctlMonitors = g_list_concat(priv->mdevctlMonitors, newmonitors);

priv->mdevctlMonitors is shared among threads, but you only acquire the
mutex just before exec'ing mdevctl in the mdevctlHandler thread, so ^this can
yield unexpected results in terms of directories monitored.

> +    }
> +
> +    /* When mdevctl creates a device, it can result in multiple notify events
> +     * emitted for a single logical change (e.g. several CHANGED events, or a
> +     * CREATED and CHANGED event followed by CHANGES_DONE_HINT). To avoid
> +     * spawning a mdevctl thread multiple times for a single logical
> +     * configuration change, try to coalesce these changes by waiting for the
> +     * CHANGES_DONE_HINT event. As a fallback,  add a timeout to trigger the
> +     * signal if that event never comes */

So there's no guaranteed pattern to monitor, is that so? IOW Even the
CHANGES_DONE_HINT may not actually arrive? That's a very poor design. I'd like
to ditch the timer as much as possible.

> +    if (event_type != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) {
> +        if (priv->mdevctlTimeout > 0)
> +            virEventRemoveTimeout(priv->mdevctlTimeout);
> +        priv->mdevctlTimeout = virEventAddTimeout(100, scheduleMdevctlHandler,
> +                                                  priv, NULL);
> +        return;
> +    }
> +
> +    scheduleMdevctlHandler(-1, priv);
> +}
> +
> +
>  static int
>  nodeStateInitialize(bool privileged,
>                      const char *root,
> @@ -2007,6 +2147,8 @@ nodeStateInitialize(bool privileged,
>      udevEventDataPtr priv = NULL;
>      struct udev *udev = NULL;
>      virThread enumThread;
> +    g_autoptr(GFile) mdevctlConfigDir = g_file_new_for_path("/etc/mdevctl.d");
> +    GError *error = NULL;

^This error isn't passed anywhere...

>  
>      if (root != NULL) {
>          virReportError(VIR_ERR_INVALID_ARG, "%s",
> @@ -2108,6 +2250,22 @@ nodeStateInitialize(bool privileged,
>      if (priv->watch == -1)
>          goto unlock;
>  
> +    /* mdevctl may add notification events in the future:
> +     * https://github.com/mdevctl/mdevctl/issues/27. For now, fall back to
> +     * monitoring the mdevctl configuration directory for changes.
> +     * mdevctl configuration is stored in a directory tree within
> +     * /etc/mdevctl.d/. There is a directory for each parent device, which
> +     * contains a file defining each mediated device */

One security aspect with this proposal that needs to mentioned is that we
should not make the directory to be monitored configurable, otherwise it's so
easy to deplete resources - simply because this patch doesn't introduce any
limit on number of spawned threads (like we have for thread pools).

> +    if (!(priv->mdevctlMonitors = monitorFileRecursively(priv,
> +                                                         mdevctlConfigDir))) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR,
> +                       _("failed to monitor mdevctl config directory: %s"),
> +                       error->message);

This error message is redundant, because monitorFileRecursively already reports
errors appending the respective GError, not to mentioned you didn't pass error
anywhere, so it would always be NULL.

Erik





[Index of Archives]     [Virt Tools]     [Libvirt Users]     [Lib OS Info]     [Fedora Users]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [KDE Users]     [Fedora Tools]

  Powered by Linux