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