nvmet: Fix possible infinite loop triggered on hot namespace removal
Subsystems (3)
Sagi Grimberg
maintainerPATCH 1/1 nvmet: Fix possible infinite loop triggered on hot namespace removal
From: Solganik Alexander <[email protected]>
When removing a namespace we delete it from the subsystem namespaces list with list_del_init which allows us to know if it is enabled or not.
The problem is that list_del_init initialize the list next and does not respect the RCU list-traversal we do on the IO path for locating a namespace. Instead we need to use list_del_rcu which is allowed to run concurrently with the _rcu list-traversal primitives (keeps list next intact) and guarantees concurrent nvmet_find_naespace forward progress.
By changing that, we cannot rely on ns->dev_link for knowing if the namspace is enabled, so add a flags entry to nvmet_ns and use atomic updates on NVMET_NS_ENABLED.
--- drivers/nvme/target/core.c | 29 +++++++++++++---------------- drivers/nvme/target/nvmet.h | 5 +++++ 2 files changed, 18 insertions(+), 16 deletions(-)
drivers/nvme/target/core.c +13 -16
@@ -261,11 +261,10 @@ int nvmet_ns_enable(struct nvmet_ns *ns) {struct nvmet_subsys *subsys = ns->subsys;struct nvmet_ctrl *ctrl; -int ret =0 ; +int ret; -mutex_lock (&subsys->lock); -if (!list_empty (&ns->dev_link)) - goto out_unlock; +if (test_and_set_bit (NVMET_NS_ENABLED , &ns->flags)) +return 0 ; ns->bdev =blkdev_get_by_path (ns->device_path,FMODE_READ |FMODE_WRITE ,NULL );
@@ -274,7 +273,7 @@ int nvmet_ns_enable(struct nvmet_ns *ns) ns->device_path, PTR_ERR(ns->bdev)); ret =PTR_ERR (ns->bdev); ns->bdev =NULL ; - goto out_unlock; + goto out; } ns->size =i_size_read (ns->bdev->bd_inode);
@@ -292,6 +291,7 @@ int nvmet_ns_enable(struct nvmet_ns *ns)* The namespaces list needs to be sorted to simplify the implementation * of the Identify Namepace List subcommand. */ +mutex_lock (&subsys->lock);if (list_empty (&subsys->namespaces)) {list_add_tail_rcu (&ns->dev_link, &subsys->namespaces); }else {
@@ -308,15 +308,15 @@ int nvmet_ns_enable(struct nvmet_ns *ns)list_for_each_entry (ctrl, &subsys->ctrls, subsys_entry)nvmet_add_async_event (ctrl,NVME_AER_TYPE_NOTICE ,0 ,0 ); - - ret =0 ; -out_unlock:mutex_unlock (&subsys->lock); -return ret; + +return 0 ; + out_blkdev_put:blkdev_put (ns->bdev,FMODE_WRITE |FMODE_READ ); ns->bdev =NULL ; - goto out_unlock; +out: +return ret; }void nvmet_ns_disable (struct nvmet_ns *ns)
@@ -324,13 +324,10 @@ void nvmet_ns_disable(struct nvmet_ns *ns)struct nvmet_subsys *subsys = ns->subsys;struct nvmet_ctrl *ctrl; -mutex_lock (&subsys->lock); -if (list_empty (&ns->dev_link)) { -mutex_unlock (&subsys->lock); +if (!test_and_clear_bit (NVMET_NS_ENABLED , &ns->flags))return ; - } -list_del_init (&ns->dev_link); -mutex_unlock (&subsys->lock); + +list_del_rcu (&ns->dev_link);/* * Now that we removed the namespaces from the lookup list, we
drivers/nvme/target/nvmet.h +5 -0
@@ -38,6 +38,10 @@#define IPO_IATTR_CONNECT_SQE (x) \ (cpu_to_le32(offsetof(struct nvmf_connect_command, x))) +enum nvmet_ns_flags { +NVMET_NS_ENABLED = (1 <<0 ), +}; +struct nvmet_ns {struct list_head dev_link;struct percpu_ref ref;
@@ -47,6 +51,7 @@ struct nvmet_ns {loff_t size;u8 nguid[16 ]; +unsigned long flags;struct nvmet_subsys *subsys;const char *device_path;
Signature · 1 line
2.7.4
PATCH 1/1 nvmet: Fix possible infinite loop triggered on hot namespace removal
Quoted 5 lines
void
nvmet_ns_disable(struct nvmet_ns *ns)drivers/nvme/target/core.c +0
-0@@ -324,13 +324,10 @@ void nvmet_ns_disable(struct nvmet_ns *ns)struct nvmet_subsys *subsys = ns->subsys;struct nvmet_ctrl *ctrl;
drivers/nvme/target/core.c void nvmet_ns_disable(struct nvmet_ns *ns) · line 330
Quoted 10 lines
drivers/nvme/target/core.c +3
-6-mutex_lock (&subsys->lock); -if (list_empty (&ns->dev_link)) { -mutex_unlock (&subsys->lock); +if (!test_and_clear_bit (NVMET_NS_ENABLED , &ns->flags))return ; - } -list_del_init (&ns->dev_link); -mutex_unlock (&subsys->lock); + +list_del_rcu (&ns->dev_link);
We'll still need the lock here, don't we?
Sagi Grimberg
maintainerPATCH 1/1 nvmet: Fix possible infinite loop triggered on hot namespace removal
Quoted 6 lines
Quoted 6 lines
void
nvmet_ns_disable(struct nvmet_ns *ns)drivers/nvme/target/core.c +0
-0@@ -324,13 +324,10 @@ void nvmet_ns_disable(struct nvmet_ns *ns)struct nvmet_subsys *subsys = ns->subsys;struct nvmet_ctrl *ctrl;
Quoted 12 lines
drivers/nvme/target/core.c void nvmet_ns_disable(struct nvmet_ns *ns) · line 330
Quoted 10 lines
drivers/nvme/target/core.c +3
-6-mutex_lock (&subsys->lock); -if (list_empty (&ns->dev_link)) { -mutex_unlock (&subsys->lock); +if (!test_and_clear_bit (NVMET_NS_ENABLED , &ns->flags))return ; - } -list_del_init (&ns->dev_link); -mutex_unlock (&subsys->lock); + +list_del_rcu (&ns->dev_link);We'll still need the lock here, don't we?
we do... And we also need to protect with the subsys lock on nvmet_ns_disable as a whole against concurrent enable/disable (unless we want to add a new lock to nvmet_ns)