a41c76
From b155913f796b313ce969ae318beb66e3e35d13dc Mon Sep 17 00:00:00 2001
a41c76
Message-Id: <b155913f796b313ce969ae318beb66e3e35d13dc@dist-git>
a41c76
From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= <berrange@redhat.com>
a41c76
Date: Thu, 11 Jun 2020 18:21:26 +0100
a41c76
Subject: [PATCH] nodedev: fix race in API usage vs initial device enumeration
a41c76
MIME-Version: 1.0
a41c76
Content-Type: text/plain; charset=UTF-8
a41c76
Content-Transfer-Encoding: 8bit
a41c76
a41c76
During startup the udev node device driver impl uses a background thread
a41c76
to populate the list of devices to avoid blocking the daemon startup
a41c76
entirely. There is no synchronization to the public APIs, so it is
a41c76
possible for an application to start calling APIs before the device
a41c76
initialization is complete.
a41c76
a41c76
This was not a problem in the old approach where libvirtd was started
a41c76
on boot, as initialization would easily complete before any APIs were
a41c76
called.
a41c76
a41c76
With the use of socket activation, however, APIs are invoked from the
a41c76
very moment the daemon starts. This is easily seen by doing a
a41c76
a41c76
  'virsh -c nodedev:///system list'
a41c76
a41c76
the first time it runs it will only show one or two devices. The second
a41c76
time it runs it will show all devices. The solution is to introduce a
a41c76
flag and condition variable for APIs to synchronize against before
a41c76
returning any data.
a41c76
a41c76
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
a41c76
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
a41c76
(cherry picked from commit 008abeb03c262149b756ad5a226ff6cbc5e37e2c)
a41c76
a41c76
https://bugzilla.redhat.com/show_bug.cgi?id=1846237
a41c76
https://bugzilla.redhat.com/show_bug.cgi?id=1845459
a41c76
Message-Id: <20200611172126.269081-2-berrange@redhat.com>
a41c76
Reviewed-by: Jiri Denemark <jdenemar@redhat.com>
a41c76
---
a41c76
 src/conf/virnodedeviceobj.h          |  2 ++
a41c76
 src/node_device/node_device_driver.c | 44 ++++++++++++++++++++++++++++
a41c76
 src/node_device/node_device_hal.c    | 15 ++++++++++
a41c76
 src/node_device/node_device_udev.c   | 13 ++++++++
a41c76
 4 files changed, 74 insertions(+)
a41c76
a41c76
diff --git a/src/conf/virnodedeviceobj.h b/src/conf/virnodedeviceobj.h
a41c76
index c4d3c55d73..c9df8dedab 100644
a41c76
--- a/src/conf/virnodedeviceobj.h
a41c76
+++ b/src/conf/virnodedeviceobj.h
a41c76
@@ -36,6 +36,8 @@ typedef struct _virNodeDeviceDriverState virNodeDeviceDriverState;
a41c76
 typedef virNodeDeviceDriverState *virNodeDeviceDriverStatePtr;
a41c76
 struct _virNodeDeviceDriverState {
a41c76
     virMutex lock;
a41c76
+    virCond initCond;
a41c76
+    bool initialized;
a41c76
 
a41c76
     /* pid file FD, ensures two copies of the driver can't use the same root */
a41c76
     int lockFD;
a41c76
diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c
a41c76
index b630be4399..a894f7265f 100644
a41c76
--- a/src/node_device/node_device_driver.c
a41c76
+++ b/src/node_device/node_device_driver.c
a41c76
@@ -162,6 +162,22 @@ nodeDeviceUnlock(void)
a41c76
 }
a41c76
 
a41c76
 
a41c76
+static int
a41c76
+nodeDeviceWaitInit(void)
a41c76
+{
a41c76
+    nodeDeviceLock();
a41c76
+    while (!driver->initialized) {
a41c76
+        if (virCondWait(&driver->initCond, &driver->lock) < 0) {
a41c76
+            virReportSystemError(errno, "%s",
a41c76
+                                 _("failed to wait on condition"));
a41c76
+            nodeDeviceUnlock();
a41c76
+            return -1;
a41c76
+        }
a41c76
+    }
a41c76
+    nodeDeviceUnlock();
a41c76
+    return 0;
a41c76
+}
a41c76
+
a41c76
 int
a41c76
 nodeNumOfDevices(virConnectPtr conn,
a41c76
                  const char *cap,
a41c76
@@ -172,6 +188,9 @@ nodeNumOfDevices(virConnectPtr conn,
a41c76
 
a41c76
     virCheckFlags(0, -1);
a41c76
 
a41c76
+    if (nodeDeviceWaitInit() < 0)
a41c76
+        return -1;
a41c76
+
a41c76
     return virNodeDeviceObjListNumOfDevices(driver->devs, conn, cap,
a41c76
                                             virNodeNumOfDevicesCheckACL);
a41c76
 }
a41c76
@@ -189,6 +208,9 @@ nodeListDevices(virConnectPtr conn,
a41c76
 
a41c76
     virCheckFlags(0, -1);
a41c76
 
a41c76
+    if (nodeDeviceWaitInit() < 0)
a41c76
+        return -1;
a41c76
+
a41c76
     return virNodeDeviceObjListGetNames(driver->devs, conn,
a41c76
                                         virNodeListDevicesCheckACL,
a41c76
                                         cap, names, maxnames);
a41c76
@@ -205,6 +227,9 @@ nodeConnectListAllNodeDevices(virConnectPtr conn,
a41c76
     if (virConnectListAllNodeDevicesEnsureACL(conn) < 0)
a41c76
         return -1;
a41c76
 
a41c76
+    if (nodeDeviceWaitInit() < 0)
a41c76
+        return -1;
a41c76
+
a41c76
     return virNodeDeviceObjListExport(conn, driver->devs, devices,
a41c76
                                       virConnectListAllNodeDevicesCheckACL,
a41c76
                                       flags);
a41c76
@@ -234,6 +259,9 @@ nodeDeviceLookupByName(virConnectPtr conn,
a41c76
     virNodeDeviceDefPtr def;
a41c76
     virNodeDevicePtr device = NULL;
a41c76
 
a41c76
+    if (nodeDeviceWaitInit() < 0)
a41c76
+        return NULL;
a41c76
+
a41c76
     if (!(obj = nodeDeviceObjFindByName(name)))
a41c76
         return NULL;
a41c76
     def = virNodeDeviceObjGetDef(obj);
a41c76
@@ -262,6 +290,9 @@ nodeDeviceLookupSCSIHostByWWN(virConnectPtr conn,
a41c76
 
a41c76
     virCheckFlags(0, NULL);
a41c76
 
a41c76
+    if (nodeDeviceWaitInit() < 0)
a41c76
+        return NULL;
a41c76
+
a41c76
     if (!(obj = virNodeDeviceObjListFindSCSIHostByWWNs(driver->devs,
a41c76
                                                        wwnn, wwpn)))
a41c76
         return NULL;
a41c76
@@ -475,6 +506,10 @@ nodeDeviceCreateXML(virConnectPtr conn,
a41c76
     const char *virt_type = NULL;
a41c76
 
a41c76
     virCheckFlags(0, NULL);
a41c76
+
a41c76
+    if (nodeDeviceWaitInit() < 0)
a41c76
+        return NULL;
a41c76
+
a41c76
     virt_type  = virConnectGetType(conn);
a41c76
 
a41c76
     if (!(def = virNodeDeviceDefParseString(xmlDesc, CREATE_DEVICE, virt_type)))
a41c76
@@ -519,6 +554,9 @@ nodeDeviceDestroy(virNodeDevicePtr device)
a41c76
     char *wwnn = NULL, *wwpn = NULL;
a41c76
     unsigned int parent_host;
a41c76
 
a41c76
+    if (nodeDeviceWaitInit() < 0)
a41c76
+        return -1;
a41c76
+
a41c76
     if (!(obj = nodeDeviceObjFindByName(device->name)))
a41c76
         return -1;
a41c76
     def = virNodeDeviceObjGetDef(obj);
a41c76
@@ -574,6 +612,9 @@ nodeConnectNodeDeviceEventRegisterAny(virConnectPtr conn,
a41c76
     if (virConnectNodeDeviceEventRegisterAnyEnsureACL(conn) < 0)
a41c76
         return -1;
a41c76
 
a41c76
+    if (nodeDeviceWaitInit() < 0)
a41c76
+        return -1;
a41c76
+
a41c76
     if (virNodeDeviceEventStateRegisterID(conn, driver->nodeDeviceEventState,
a41c76
                                           device, eventID, callback,
a41c76
                                           opaque, freecb, &callbackID) < 0)
a41c76
@@ -590,6 +631,9 @@ nodeConnectNodeDeviceEventDeregisterAny(virConnectPtr conn,
a41c76
     if (virConnectNodeDeviceEventDeregisterAnyEnsureACL(conn) < 0)
a41c76
         return -1;
a41c76
 
a41c76
+    if (nodeDeviceWaitInit() < 0)
a41c76
+        return -1;
a41c76
+
a41c76
     if (virObjectEventStateDeregisterID(conn,
a41c76
                                         driver->nodeDeviceEventState,
a41c76
                                         callbackID, true) < 0)
a41c76
diff --git a/src/node_device/node_device_hal.c b/src/node_device/node_device_hal.c
a41c76
index 4cef7c2c12..0d48895b66 100644
a41c76
--- a/src/node_device/node_device_hal.c
a41c76
+++ b/src/node_device/node_device_hal.c
a41c76
@@ -603,6 +603,15 @@ nodeStateInitialize(bool privileged G_GNUC_UNUSED,
a41c76
         VIR_FREE(driver);
a41c76
         return VIR_DRV_STATE_INIT_ERROR;
a41c76
     }
a41c76
+
a41c76
+    if (virCondInit(&driver->initCond) < 0) {
a41c76
+        virReportSystemError(errno, "%s",
a41c76
+                             _("Unable to initialize condition variable"));
a41c76
+        virMutexDestroy(&driver->lock);
a41c76
+        VIR_FREE(driver);
a41c76
+        return VIR_DRV_STATE_INIT_ERROR;
a41c76
+    }
a41c76
+
a41c76
     nodeDeviceLock();
a41c76
 
a41c76
     if (privileged) {
a41c76
@@ -693,6 +702,11 @@ nodeStateInitialize(bool privileged G_GNUC_UNUSED,
a41c76
     }
a41c76
     VIR_FREE(udi);
a41c76
 
a41c76
+    nodeDeviceLock();
a41c76
+    driver->initialized = true;
a41c76
+    nodeDeviceUnlock();
a41c76
+    virCondBroadcast(&driver->initCond);
a41c76
+
a41c76
     return VIR_DRV_STATE_INIT_COMPLETE;
a41c76
 
a41c76
  failure:
a41c76
@@ -725,6 +739,7 @@ nodeStateCleanup(void)
a41c76
 
a41c76
         VIR_FREE(driver->stateDir);
a41c76
         nodeDeviceUnlock();
a41c76
+        virCondDestroy(&driver->initCond);
a41c76
         virMutexDestroy(&driver->lock);
a41c76
         VIR_FREE(driver);
a41c76
         return 0;
a41c76
diff --git a/src/node_device/node_device_udev.c b/src/node_device/node_device_udev.c
a41c76
index 4b33dc25d8..ae3d081e66 100644
a41c76
--- a/src/node_device/node_device_udev.c
a41c76
+++ b/src/node_device/node_device_udev.c
a41c76
@@ -1476,6 +1476,7 @@ nodeStateCleanup(void)
a41c76
         virPidFileRelease(driver->stateDir, "driver", driver->lockFD);
a41c76
 
a41c76
     VIR_FREE(driver->stateDir);
a41c76
+    virCondDestroy(&driver->initCond);
a41c76
     virMutexDestroy(&driver->lock);
a41c76
     VIR_FREE(driver);
a41c76
 
a41c76
@@ -1743,6 +1744,11 @@ nodeStateInitializeEnumerate(void *opaque)
a41c76
     if (udevEnumerateDevices(udev) != 0)
a41c76
         goto error;
a41c76
 
a41c76
+    nodeDeviceLock();
a41c76
+    driver->initialized = true;
a41c76
+    nodeDeviceUnlock();
a41c76
+    virCondBroadcast(&driver->initCond);
a41c76
+
a41c76
     return;
a41c76
 
a41c76
  error:
a41c76
@@ -1798,6 +1804,13 @@ nodeStateInitialize(bool privileged,
a41c76
         VIR_FREE(driver);
a41c76
         return VIR_DRV_STATE_INIT_ERROR;
a41c76
     }
a41c76
+    if (virCondInit(&driver->initCond) < 0) {
a41c76
+        virReportSystemError(errno, "%s",
a41c76
+                             _("Unable to initialize condition variable"));
a41c76
+        virMutexDestroy(&driver->lock);
a41c76
+        VIR_FREE(driver);
a41c76
+        return VIR_DRV_STATE_INIT_ERROR;
a41c76
+    }
a41c76
 
a41c76
     driver->privileged = privileged;
a41c76
 
a41c76
-- 
a41c76
2.27.0
a41c76