]> Pileus Git - ~andy/linux/blobdiff - drivers/target/iscsi/iscsi_target.c
target/iscsi: Fix network portal creation race
[~andy/linux] / drivers / target / iscsi / iscsi_target.c
index 2a52752a99371e3f7bbb8f617d9ee7cac94e48ce..a99637e9e82017757239db64673986b71adcffa7 100644 (file)
@@ -52,7 +52,7 @@
 static LIST_HEAD(g_tiqn_list);
 static LIST_HEAD(g_np_list);
 static DEFINE_SPINLOCK(tiqn_lock);
-static DEFINE_SPINLOCK(np_lock);
+static DEFINE_MUTEX(np_lock);
 
 static struct idr tiqn_idr;
 struct idr sess_idr;
@@ -307,6 +307,9 @@ bool iscsit_check_np_match(
        return false;
 }
 
+/*
+ * Called with mutex np_lock held
+ */
 static struct iscsi_np *iscsit_get_np(
        struct __kernel_sockaddr_storage *sockaddr,
        int network_transport)
@@ -314,11 +317,10 @@ static struct iscsi_np *iscsit_get_np(
        struct iscsi_np *np;
        bool match;
 
-       spin_lock_bh(&np_lock);
        list_for_each_entry(np, &g_np_list, np_list) {
-               spin_lock(&np->np_thread_lock);
+               spin_lock_bh(&np->np_thread_lock);
                if (np->np_thread_state != ISCSI_NP_THREAD_ACTIVE) {
-                       spin_unlock(&np->np_thread_lock);
+                       spin_unlock_bh(&np->np_thread_lock);
                        continue;
                }
 
@@ -330,13 +332,11 @@ static struct iscsi_np *iscsit_get_np(
                         * while iscsi_tpg_add_network_portal() is called.
                         */
                        np->np_exports++;
-                       spin_unlock(&np->np_thread_lock);
-                       spin_unlock_bh(&np_lock);
+                       spin_unlock_bh(&np->np_thread_lock);
                        return np;
                }
-               spin_unlock(&np->np_thread_lock);
+               spin_unlock_bh(&np->np_thread_lock);
        }
-       spin_unlock_bh(&np_lock);
 
        return NULL;
 }
@@ -350,16 +350,22 @@ struct iscsi_np *iscsit_add_np(
        struct sockaddr_in6 *sock_in6;
        struct iscsi_np *np;
        int ret;
+
+       mutex_lock(&np_lock);
+
        /*
         * Locate the existing struct iscsi_np if already active..
         */
        np = iscsit_get_np(sockaddr, network_transport);
-       if (np)
+       if (np) {
+               mutex_unlock(&np_lock);
                return np;
+       }
 
        np = kzalloc(sizeof(struct iscsi_np), GFP_KERNEL);
        if (!np) {
                pr_err("Unable to allocate memory for struct iscsi_np\n");
+               mutex_unlock(&np_lock);
                return ERR_PTR(-ENOMEM);
        }
 
@@ -382,6 +388,7 @@ struct iscsi_np *iscsit_add_np(
        ret = iscsi_target_setup_login_socket(np, sockaddr);
        if (ret != 0) {
                kfree(np);
+               mutex_unlock(&np_lock);
                return ERR_PTR(ret);
        }
 
@@ -390,6 +397,7 @@ struct iscsi_np *iscsit_add_np(
                pr_err("Unable to create kthread: iscsi_np\n");
                ret = PTR_ERR(np->np_thread);
                kfree(np);
+               mutex_unlock(&np_lock);
                return ERR_PTR(ret);
        }
        /*
@@ -400,10 +408,10 @@ struct iscsi_np *iscsit_add_np(
         * point because iscsi_np has not been added to g_np_list yet.
         */
        np->np_exports = 1;
+       np->np_thread_state = ISCSI_NP_THREAD_ACTIVE;
 
-       spin_lock_bh(&np_lock);
        list_add_tail(&np->np_list, &g_np_list);
-       spin_unlock_bh(&np_lock);
+       mutex_unlock(&np_lock);
 
        pr_debug("CORE[0] - Added Network Portal: %s:%hu on %s\n",
                np->np_ip, np->np_port, np->np_transport->name);
@@ -469,9 +477,9 @@ int iscsit_del_np(struct iscsi_np *np)
 
        np->np_transport->iscsit_free_np(np);
 
-       spin_lock_bh(&np_lock);
+       mutex_lock(&np_lock);
        list_del(&np->np_list);
-       spin_unlock_bh(&np_lock);
+       mutex_unlock(&np_lock);
 
        pr_debug("CORE[0] - Removed Network Portal: %s:%hu on %s\n",
                np->np_ip, np->np_port, np->np_transport->name);