[libvirt PATCH 09/16] nodedev: add an mdevctl thread

Jonathon Jongsma jjongsma at redhat.com
Thu Jul 16 22:21:39 UTC 2020


We need to peridocally query mdevctl for changes to device definitions
since an administrator can define new devices with mdevctl outside of
libvirt.

In order to do this, a new thread is created to handle the querying. 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 directories for changes to files. When a change is
detected, we query mdevctl and update our device list.

Signed-off-by: Jonathon Jongsma <jjongsma at redhat.com>
---
 src/node_device/node_device_udev.c | 182 +++++++++++++++++++++++++++++
 1 file changed, 182 insertions(+)

diff --git a/src/node_device/node_device_udev.c b/src/node_device/node_device_udev.c
index e06584a3dc..02017b5325 100644
--- a/src/node_device/node_device_udev.c
+++ b/src/node_device/node_device_udev.c
@@ -41,6 +41,7 @@
 #include "virnetdev.h"
 #include "virmdev.h"
 #include "virutil.h"
+#include <gio/gio.h>
 
 #include "configmake.h"
 
@@ -66,6 +67,11 @@ struct _udevEventData {
     virCond threadCond;
     bool threadQuit;
     bool dataReady;
+
+    virThread mdevctlThread;
+    virCond mdevctlCond;
+    bool mdevctlReady;
+    GList *mdevctl_monitors;
 };
 
 static virClassPtr udevEventDataClass;
@@ -85,8 +91,10 @@ udevEventDataDispose(void *obj)
     udev = udev_monitor_get_udev(priv->udev_monitor);
     udev_monitor_unref(priv->udev_monitor);
     udev_unref(udev);
+    g_list_free_full(priv->mdevctl_monitors, g_object_unref);
 
     virCondDestroy(&priv->threadCond);
+    virCondDestroy(&priv->mdevctlCond);
 }
 
 
@@ -117,6 +125,11 @@ udevEventDataNew(void)
         return NULL;
     }
 
+    if (virCondInit(&ret->mdevctlCond) < 0) {
+        virObjectUnref(ret);
+        return NULL;
+    }
+
     ret->watch = -1;
     return ret;
 }
@@ -1520,6 +1533,7 @@ nodeStateCleanup(void)
         virObjectLock(priv);
         priv->threadQuit = true;
         virCondSignal(&priv->threadCond);
+        virCondSignal(&priv->mdevctlCond);
         virObjectUnlock(priv);
         virThreadJoin(&priv->th);
     }
@@ -1601,6 +1615,40 @@ udevEventMonitorSanityCheck(udevEventDataPtr priv,
 }
 
 
+/* Thread to query mdevctl for the current state of persistent mediated device
+ * defintions when any changes are detected */
+static
+void mdevctlThread(void *opaque G_GNUC_UNUSED)
+{
+    udevEventDataPtr priv = driver->privateData;
+
+    while (1) {
+        virObjectLock(priv);
+        while (!priv->mdevctlReady && !priv->threadQuit) {
+            if (virCondWait(&priv->mdevctlCond, &priv->parent.lock)) {
+                virReportSystemError(errno, "%s",
+                                     _("handler failed to wait on condition"));
+                virObjectUnlock(priv);
+                return;
+            }
+        }
+
+        if (priv->threadQuit) {
+            virObjectUnlock(priv);
+            return;
+        }
+
+        virObjectUnlock(priv);
+
+        mdevctlEnumerateDevices();
+
+        virObjectLock(priv);
+        priv->mdevctlReady = false;
+        virObjectUnlock(priv);
+    }
+}
+
+
 /**
  * udevEventHandleThread
  * @opaque: unused
@@ -1708,6 +1756,69 @@ udevEventHandleCallback(int watch G_GNUC_UNUSED,
 }
 
 
+static int timeout_id = 0;
+
+static gboolean
+signalMdevctlThread(void *user_data)
+{
+    udevEventDataPtr priv = user_data;
+
+    if (timeout_id) {
+        g_source_remove(timeout_id);
+        timeout_id = 0;
+    }
+
+    virObjectLock(priv);
+    priv->mdevctlReady = true;
+    virCondSignal(&priv->mdevctlCond);
+    virObjectUnlock(priv);
+
+    return G_SOURCE_REMOVE;
+}
+
+
+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 (g_file_query_file_type(file, G_FILE_QUERY_INFO_NONE, NULL) ==
+        G_FILE_TYPE_DIRECTORY) {
+        GFileMonitor *mon;
+
+        if ((mon = g_file_monitor(file, G_FILE_MONITOR_NONE, NULL, NULL))) {
+            g_signal_connect(mon, "changed",
+                             G_CALLBACK(mdevctlEventHandleCallback),
+                             user_data);
+            virObjectLock(priv);
+            priv->mdevctl_monitors = g_list_append(priv->mdevctl_monitors, mon);
+            virObjectUnlock(priv);
+        }
+    }
+
+    /* Sometimes a single configuration change results in multiple notify
+     * events (e.g. several CHANGED events, or a CREATED and CHANGED event
+     * followed by CHANGES_DONE_HINT).  To avoid triggering the mdevctl thread
+     * multiple times for a single 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 */
+    if (event_type == G_FILE_MONITOR_EVENT_CREATED ||
+        event_type == G_FILE_MONITOR_EVENT_CHANGED) {
+        if (timeout_id)
+            g_source_remove(timeout_id);
+        timeout_id = g_timeout_add(100, signalMdevctlThread, priv);
+        return;
+    }
+
+    signalMdevctlThread(priv);
+}
+
+
 /* DMI is intel-compatible specific */
 #if defined(__x86_64__) || defined(__i386__) || defined(__amd64__)
 static void
@@ -1858,6 +1969,58 @@ udevPCITranslateInit(bool privileged G_GNUC_UNUSED)
 }
 
 
+/* Recursively monitors dir and any subdirectory for file changes and returns a
+ * GList of GFileMonitor objects */
+static GList*
+monitorDirRecursively(GFile *dir,
+                      GCallback changed_cb,
+                      gpointer user_data)
+{
+    GList *monitors = NULL;
+    g_autoptr(GFileInfo) dirinfo = NULL;
+    g_autoptr(GError) error = NULL;
+    g_autoptr(GFileEnumerator) children = NULL;
+    GFileMonitor *mon;
+
+    if (!(children = g_file_enumerate_children(dir, "standard::*",
+                                               G_FILE_QUERY_INFO_NONE, NULL, &error))) {
+        if (error->code == G_IO_ERROR_NOT_DIRECTORY)
+            return NULL;
+        goto bail;
+    }
+
+    if (!(mon = g_file_monitor(dir, G_FILE_MONITOR_NONE, NULL, &error)))
+        goto bail;
+
+    g_signal_connect(mon, "changed", changed_cb, user_data);
+    monitors = g_list_append(monitors, mon);
+
+    while (true) {
+        GFileInfo *info;
+        GFile *child;
+        GList *child_monitors = NULL;
+
+        if (!g_file_enumerator_iterate(children, &info, &child, NULL, &error))
+            goto bail;
+        if (!info)
+            break;
+
+        child_monitors = monitorDirRecursively(child, changed_cb, user_data);
+        if (child_monitors)
+            monitors = g_list_concat(monitors, child_monitors);
+    }
+
+    return monitors;
+
+ bail:
+    if (error)
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("Unable to monitor directory: %s"), error->message);
+
+    g_list_free_full(monitors, g_object_unref);
+    return NULL;
+}
+
 static int
 nodeStateInitialize(bool privileged,
                     const char *root,
@@ -1867,6 +2030,8 @@ nodeStateInitialize(bool privileged,
     udevEventDataPtr priv = NULL;
     struct udev *udev = NULL;
     virThread enumThread;
+    g_autoptr(GError) error = NULL;
+    g_autoptr(GFile) mdevctlConfigDir = g_file_new_for_path("/etc/mdevctl.d");
 
     if (root != NULL) {
         virReportError(VIR_ERR_INVALID_ARG, "%s",
@@ -1969,6 +2134,23 @@ nodeStateInitialize(bool privileged,
     if (priv->watch == -1)
         goto unlock;
 
+    if (virThreadCreateFull(&priv->mdevctlThread, true, mdevctlThread,
+                            "mdevctl-event", false, NULL) < 0) {
+        virReportSystemError(errno, "%s",
+                             _("failed to create mdevctl handler thread"));
+        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 */
+    priv->mdevctl_monitors = monitorDirRecursively(mdevctlConfigDir,
+                                                   G_CALLBACK(mdevctlEventHandleCallback),
+                                                   priv);
+
     virObjectUnlock(priv);
 
     /* Create a fictional 'computer' device to root the device tree. */
-- 
2.21.3




More information about the libvir-list mailing list