From: Jiri Pirko <[email protected]>

Prepare namespace moves for per-netns names. Check user-initiated moves for
destination-name conflicts before disabling the device, keep same-netns
moves as no-ops, and make teardown moves detach from the exiting namespace
even if fallback naming fails.

Signed-off-by: Jiri Pirko <[email protected]>
---
 drivers/infiniband/core/device.c | 155 ++++++++++++++++++++++++++-----
 drivers/infiniband/core/nldev.c  |   3 +
 2 files changed, 137 insertions(+), 21 deletions(-)

diff --git a/drivers/infiniband/core/device.c b/drivers/infiniband/core/device.c
index de610f52c9b2..8d169658e312 100644
--- a/drivers/infiniband/core/device.c
+++ b/drivers/infiniband/core/device.c
@@ -268,7 +268,7 @@ static struct notifier_block ibdev_lsm_nb = {
 };
 
 static int rdma_dev_change_netns(struct ib_device *device, struct net *cur_net,
-                                struct net *net);
+                                struct net *net, const char *fallback_pattern);
 
 /* Pointer to the RCU head at the start of the ib_port_data array */
 struct ib_port_data_rcu {
@@ -437,7 +437,8 @@ int ib_device_set_dim(struct ib_device *ibdev, u8 use_dim)
 }
 
 /* Pick a free index for the '%d' style @name pattern. */
-static int alloc_name_id(struct net *net, const char *name)
+static int __alloc_name_id(struct net *net, const char *name,
+                          const struct ib_device *skip)
 {
        struct ib_device *device;
        unsigned long index;
@@ -450,6 +451,8 @@ static int alloc_name_id(struct net *net, const char *name)
        xa_for_each (&devices, index, device) {
                char buf[IB_DEVICE_NAME_MAX];
 
+               if (device == skip)
+                       continue;
                if (sscanf(dev_name(&device->dev), name, &i) != 1)
                        continue;
                if (i < 0 || i >= INT_MAX)
@@ -469,6 +472,11 @@ static int alloc_name_id(struct net *net, const char *name)
        return rc;
 }
 
+static int alloc_name_id(struct net *net, const char *name)
+{
+       return __alloc_name_id(net, name, NULL);
+}
+
 static int alloc_name(struct ib_device *ibdev, const char *name)
 {
        int id;
@@ -1160,8 +1168,17 @@ static void rdma_dev_exit_net(struct net *net)
 
                /*
                 * If the real device is in the NS then move it back to init.
+                * Provide a fallback pattern so a name conflict in init_net
+                * cannot make the teardown move fail.
                 */
-               rdma_dev_change_netns(dev, net, &init_net);
+               if (net_eq(net, read_pnet(&dev->coredev.rdma_net))) {
+                       ret = rdma_dev_change_netns(dev, net, &init_net,
+                                                   "ibdev%d");
+                       if (ret)
+                               WARN(1,
+                                    "Failed to move RDMA device %s to init_net 
on netns exit: %d\n",
+                                    dev_name(&dev->dev), ret);
+               }
 
                put_device(&dev->dev);
                down_read(&devices_rwsem);
@@ -1680,14 +1697,71 @@ void ib_unregister_device_queued(struct ib_device 
*ib_dev)
 }
 EXPORT_SYMBOL(ib_unregister_device_queued);
 
+static bool rdma_dev_name_in_netns(struct ib_device *skip, struct net *net,
+                                  const char *name)
+{
+       struct ib_device *device;
+       unsigned long index;
+
+       lockdep_assert_held_write(&devices_rwsem);
+
+       xa_for_each(&devices, index, device)
+               if (device != skip &&
+                   !strcmp(name, dev_name(&device->dev)))
+                       return true;
+
+       return false;
+}
+
+/*
+ * Choose the name @device should use in net namespace @net: keep the current
+ * name when it is free, otherwise use a trusted '%d' @fallback_pattern
+ * (namespace teardown) to pick a free index. The caller must hold the write
+ * side of devices_rwsem.
+ */
+static int rdma_dev_pick_netns_name(struct ib_device *device, struct net *net,
+                                   const char *fallback_pattern,
+                                   char *buf, size_t buf_len,
+                                   const char **new_name)
+{
+       int id;
+
+       lockdep_assert_held_write(&devices_rwsem);
+
+       if (!rdma_dev_name_in_netns(device, net, dev_name(&device->dev))) {
+               *new_name = dev_name(&device->dev);
+               return 0;
+       }
+
+       if (!fallback_pattern)
+               return -EEXIST;
+
+       snprintf(buf, buf_len, "ibdev%u", device->index);
+       if (!rdma_dev_name_in_netns(device, net, buf)) {
+               *new_name = buf;
+               return 0;
+       }
+
+       id = __alloc_name_id(net, fallback_pattern, device);
+       if (id < 0)
+               return id;
+       snprintf(buf, buf_len, fallback_pattern, id);
+       *new_name = buf;
+       return 0;
+}
+
 /*
  * The caller must pass in a device that has the kref held and the refcount
  * released. If the device is in cur_net and still registered then it is moved
  * into net.
+ *
+ * Naming rules are handled by rdma_dev_pick_netns_name().
  */
 static int rdma_dev_change_netns(struct ib_device *device, struct net *cur_net,
-                                struct net *net)
+                                struct net *net, const char *fallback_pattern)
 {
+       char buf[IB_DEVICE_NAME_MAX];
+       const char *new_name;
        int ret2 = -EINVAL;
        int ret;
 
@@ -1704,30 +1778,63 @@ static int rdma_dev_change_netns(struct ib_device 
*device, struct net *cur_net,
                goto out;
        }
 
+       if (!fallback_pattern) {
+               /*
+                * Reject a predictable name conflict before tearing anything
+                * down, so a doomed user move does not disable a live device.
+                */
+               down_write(&devices_rwsem);
+               ret = rdma_dev_pick_netns_name(device, net, fallback_pattern,
+                                              buf, sizeof(buf), &new_name);
+               up_write(&devices_rwsem);
+               if (ret)
+                       goto out;
+       }
+
        kobject_uevent(&device->dev.kobj, KOBJ_REMOVE);
        disable_device(device);
 
        /*
-        * At this point no one can be using the device, so it is safe to
-        * change the namespace.
+        * Recompute the destination name under the write side of devices_rwsem
+        * now that the device is disabled, closing races with a concurrent
+        * registration or rename, then publish the new namespace at the sysfs
+        * level.
         */
-       write_pnet(&device->coredev.rdma_net, net);
+       down_write(&devices_rwsem);
+       ret = rdma_dev_pick_netns_name(device, net, fallback_pattern, buf,
+                                      sizeof(buf), &new_name);
+       if (ret) {
+               if (fallback_pattern) {
+                       WARN(1,
+                            "%s: failed to pick device name during namespace 
teardown: %d\n",
+                            __func__, ret);
+                       write_pnet(&device->coredev.rdma_net, net);
+                       ret = 0;
+               }
+               goto rename_done;
+       }
 
-       down_read(&devices_rwsem);
-       /*
-        * Currently rdma devices are system wide unique. So the device name
-        * is guaranteed free in the new namespace. Publish the new namespace
-        * at the sysfs level.
-        */
-       ret = device_rename(&device->dev, dev_name(&device->dev));
-       up_read(&devices_rwsem);
+       write_pnet(&device->coredev.rdma_net, net);
+       ret = device_rename(&device->dev, new_name);
        if (ret) {
-               dev_warn(&device->dev,
-                        "%s: Couldn't rename device after namespace change\n",
-                        __func__);
-               /* Try and put things back and re-enable the device */
-               write_pnet(&device->coredev.rdma_net, cur_net);
+               if (fallback_pattern) {
+                       WARN(1,
+                            "%s: failed to rename device during namespace 
teardown: %d\n",
+                            __func__, ret);
+                       ret = 0;
+               } else {
+                       dev_warn(&device->dev,
+                                "%s: Couldn't rename device after namespace 
change\n",
+                                __func__);
+                       /* Try and put things back and re-enable the device */
+                       write_pnet(&device->coredev.rdma_net, cur_net);
+               }
+       } else {
+               strscpy(device->name, dev_name(&device->dev),
+                       IB_DEVICE_NAME_MAX);
        }
+rename_done:
+       up_write(&devices_rwsem);
 
        ret2 = enable_device_and_get(device);
        if (ret2) {
@@ -1766,6 +1873,12 @@ int ib_device_set_netns_put(struct sk_buff *skb,
                goto ns_err;
        }
 
+       /* Moving a device to the namespace it already lives in is a no-op. */
+       if (net_eq(net, read_pnet(&dev->coredev.rdma_net))) {
+               ret = 0;
+               goto ns_err;
+       }
+
        /*
         * All the ib_clients, including uverbs, are reset when the namespace is
         * changed and this cannot be blocked waiting for userspace to do
@@ -1778,7 +1891,7 @@ int ib_device_set_netns_put(struct sk_buff *skb,
 
        get_device(&dev->dev);
        ib_device_put(dev);
-       ret = rdma_dev_change_netns(dev, current->nsproxy->net_ns, net);
+       ret = rdma_dev_change_netns(dev, current->nsproxy->net_ns, net, NULL);
        put_device(&dev->dev);
 
        put_net(net);
diff --git a/drivers/infiniband/core/nldev.c b/drivers/infiniband/core/nldev.c
index f599c24b34e8..3a9ec43a16f1 100644
--- a/drivers/infiniband/core/nldev.c
+++ b/drivers/infiniband/core/nldev.c
@@ -1195,6 +1195,9 @@ static int nldev_set_doit(struct sk_buff *skb, struct 
nlmsghdr *nlh,
 
                ns_fd = nla_get_u32(tb[RDMA_NLDEV_NET_NS_FD]);
                err = ib_device_set_netns_put(skb, device, ns_fd);
+               if (err == -EEXIST)
+                       NL_SET_ERR_MSG(extack,
+                                      "Device name already exists in the 
target net namespace");
                goto put_done;
        }
 
-- 
2.54.0


Reply via email to