[PATCH 09/11] cifs: add routines to build sessions and tcons on the fly

Jeff Layton <[email protected]>
Newsgroups gmane.linux.file-systems.cifs,gmane.linux.file-systems
Message-ID <[email protected]>
This patch is rather large, but it's a bit difficult to do piecemeal...

Turn the tcon pointer in the cifs_sb into a radix tree that uses the
fsuid of the process as a key. The value is a new "tcon_link" struct
that contains info about a tcon that's under construction.

When a new process needs a tcon, it'll call cifs_sb_tcon. That will
then look up the tcon_link in the radix tree. If it exists and is
valid, it's returned.

If it doesn't exist, then we stuff a new tcon_link into the tree and
mark it as pending and then go and try to build the session/tcon.
If that works, the tcon pointer in the tcon_link is updated and the
pending flag is cleared.

If the construction fails, then we set the tcon pointer to an ERR_PTR
and clear the pending flag.

If the radix tree is searched and the tcon_link is marked pending
then we go to sleep and wait for the pending flag to be cleared.

Signed-off-by: Jeff Layton <[email protected]>
---
 fs/cifs/cifs_fs_sb.h |    7 +-
 fs/cifs/connect.c    |  202 ++++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 200 insertions(+), 9 deletions(-)

diff --git a/fs/cifs/cifs_fs_sb.h b/fs/cifs/cifs_fs_sb.h
index e658723..14c3b69 100644
--- a/fs/cifs/cifs_fs_sb.h
+++ b/fs/cifs/cifs_fs_sb.h
@@ -15,6 +15,8 @@
  *   the GNU Lesser General Public License for more details.
  *
  */
+#include <linux/radix-tree.h>
+
 #ifndef _CIFS_FS_SB_H
 #define _CIFS_FS_SB_H
 
@@ -36,8 +38,9 @@
 #define CIFS_MOUNT_MULTISES	0x8000 /* multisession mount */
 
 struct cifs_sb_info {
-	struct cifsTconInfo *tcon;	/* primary mount */
-	struct list_head nested_tcon_q;
+	struct radix_tree_root tcon_tree;
+#define CIFS_TCON_MASTER_TAG		0	/* tcon is "master" (mount) tcon */
+	spinlock_t tcon_tree_lock;
 	struct nls_table *local_nls;
 	unsigned int rsize;
 	unsigned int wsize;
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index 09a1216..bc408ad 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -106,6 +106,15 @@ struct smb_vol {
 	struct nls_table *local_nls;
 };
 
+#define TLINK_ERROR_EXPIRE	(1 * HZ)
+
+#define TCON_LINK_PENDING	1
+struct tcon_link {
+	unsigned long flags;
+	unsigned long time;
+	struct cifsTconInfo *tcon;
+};
+
 static int ipv4_connect(struct TCP_Server_Info *server);
 static int ipv6_connect(struct TCP_Server_Info *server);
 
@@ -2660,6 +2669,7 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
 	struct TCP_Server_Info *srvTcp;
 	char   *full_path;
 	char *mount_data = mount_data_global;
+	struct tcon_link *tlink;
 #ifdef CONFIG_CIFS_DFS_UPCALL
 	struct dfs_info3_param *referrals = NULL;
 	unsigned int num_referrals = 0;
@@ -2671,6 +2681,7 @@ try_mount_again:
 	pSesInfo = NULL;
 	srvTcp = NULL;
 	full_path = NULL;
+	tlink = NULL;
 
 	xid = GetXid();
 
@@ -2746,8 +2757,6 @@ try_mount_again:
 		goto remote_path_check;
 	}
 
-	cifs_sb->tcon = tcon;
-
 	/* do not care if following two calls succeed - informational */
 	if (!tcon->ipc) {
 		CIFSSMBQFSDeviceInfo(xid, tcon);
@@ -2856,6 +2865,29 @@ remote_path_check:
 #endif
 	}
 
+	/* now, hang the tcon off of the superblock */
+	tlink = kzalloc(sizeof *tlink, GFP_KERNEL);
+	if (tlink == NULL) {
+		rc = -ENOMEM;
+		goto mount_fail_check;
+	}
+
+	tlink->tcon = tcon;
+	tlink->time = jiffies;
+
+	rc = radix_tree_preload(GFP_KERNEL);
+	if (rc == -ENOMEM) {
+		kfree(tlink);
+		goto mount_fail_check;
+	}
+
+	spin_lock(&cifs_sb->tcon_tree_lock);
+	radix_tree_insert(&cifs_sb->tcon_tree, pSesInfo->linux_uid, tlink);
+	radix_tree_tag_set(&cifs_sb->tcon_tree, pSesInfo->linux_uid,
+			   CIFS_TCON_MASTER_TAG);
+	spin_unlock(&cifs_sb->tcon_tree_lock);
+	radix_tree_preload_end();
+
 mount_fail_check:
 	/* on error free sesinfo and tcon struct if needed */
 	if (rc) {
@@ -3041,19 +3073,35 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,
 int
 cifs_umount(struct super_block *sb, struct cifs_sb_info *cifs_sb)
 {
-	int rc = 0;
+	int i, ret;
 	char *tmp;
+	struct tcon_link *tlink[8];
+	unsigned long index = 0;
+
+	while (1) {
+		spin_lock(&cifs_sb->tcon_tree_lock);
+		ret = radix_tree_gang_lookup(&cifs_sb->tcon_tree,
+					     (void **)tlink, index,
+					     ARRAY_SIZE(tlink));
+		for (i = 0; i < ret; i++) {
+			index = (unsigned long)tlink[i]->tcon->ses->linux_uid;
+			radix_tree_delete(&cifs_sb->tcon_tree, index);
+		}
+		spin_unlock(&cifs_sb->tcon_tree_lock);
 
-	if (cifs_sb_tcon(cifs_sb))
-		cifs_put_tcon(cifs_sb_tcon(cifs_sb));
+		for (i = 0; i < ret; i++)
+			cifs_put_tcon(tlink[i]->tcon);
+
+		if (!ret)
+			break;
+	}
 
-	cifs_sb->tcon = NULL;
 	tmp = cifs_sb->prepath;
 	cifs_sb->prepathlen = 0;
 	cifs_sb->prepath = NULL;
 	kfree(tmp);
 
-	return rc;
+	return 0;
 }
 
 int cifs_negotiate_protocol(unsigned int xid, struct cifsSesInfo *ses)
@@ -3114,3 +3162,143 @@ int cifs_setup_session(unsigned int xid, struct cifsSesInfo *ses,
 	return rc;
 }
 
+struct cifsTconInfo *
+cifs_construct_tcon(struct cifs_sb_info *cifs_sb, uid_t fsuid)
+{
+	struct cifsTconInfo *master_tcon = cifs_sb_master_tcon(cifs_sb);
+	struct cifsSesInfo *ses;
+	struct cifsTconInfo *tcon = NULL;
+	struct smb_vol *vol_info;
+
+	vol_info = kzalloc(sizeof(*vol_info), GFP_KERNEL);
+	if (vol_info == NULL) {
+		tcon = ERR_PTR(-ENOMEM);
+		goto out;
+	}
+
+	vol_info->username = kzalloc(MAX_USERNAME_SIZE + 1, GFP_KERNEL);
+	if (vol_info->username == NULL) {
+		tcon = ERR_PTR(-ENOMEM);
+		goto out;
+	}
+
+	snprintf(vol_info->username, MAX_USERNAME_SIZE, "krb50x%x", fsuid);
+	vol_info->local_nls = cifs_sb->local_nls;
+	vol_info->linux_uid = fsuid;
+	vol_info->cred_uid = fsuid;
+	vol_info->UNC = master_tcon->treeName;
+	vol_info->retry = master_tcon->retry;
+	vol_info->nocase = master_tcon->nocase;
+	vol_info->local_lease = master_tcon->local_lease;
+	vol_info->no_linux_ext = !master_tcon->unix_ext;
+	vol_info->sectype = master_tcon->ses->secType;
+
+	ses = cifs_get_smb_ses(master_tcon->ses->server, vol_info);
+	if (IS_ERR(ses)) {
+		tcon = (struct cifsTconInfo *) ses;
+		goto out;
+	}
+
+	tcon = cifs_get_tcon(ses, vol_info);
+	if (IS_ERR(tcon)) {
+		cifs_put_smb_ses(ses);
+		goto out;
+	}
+
+	if (ses->capabilities & CAP_UNIX)
+		reset_cifs_unix_caps(0, tcon, NULL, vol_info);
+out:
+	kfree(vol_info->username);
+	kfree(vol_info);
+
+	return tcon;
+}
+
+struct cifsTconInfo *
+cifs_sb_master_tcon(struct cifs_sb_info *cifs_sb)
+{
+	struct tcon_link *tlink;
+	unsigned int ret;
+
+	spin_lock(&cifs_sb->tcon_tree_lock);
+	ret = radix_tree_gang_lookup_tag(&cifs_sb->tcon_tree, (void **) &tlink,
+					0, 1, CIFS_TCON_MASTER_TAG);
+	spin_unlock(&cifs_sb->tcon_tree_lock);
+
+	/* the master tcon should always be present */
+	if (ret == 0)
+		BUG();
+
+	return tlink->tcon;
+}
+
+static int
+cifs_sb_tcon_pending_wait(void *unused)
+{
+	schedule();
+	return signal_pending(current) ? -ERESTARTSYS : 0;
+}
+
+struct cifsTconInfo *
+cifs_sb_tcon(struct cifs_sb_info *cifs_sb)
+{
+	int ret;
+	unsigned long fsuid = (unsigned long) current_fsuid();
+	struct tcon_link *tlink, *newtlink;
+
+	if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MULTISES))
+		return cifs_sb_master_tcon(cifs_sb);
+
+	spin_lock(&cifs_sb->tcon_tree_lock);
+	tlink = radix_tree_lookup(&cifs_sb->tcon_tree, fsuid);
+	spin_unlock(&cifs_sb->tcon_tree_lock);
+
+	if (tlink == NULL) {
+		newtlink = kzalloc(sizeof(*tlink), GFP_KERNEL);
+		if (newtlink == NULL)
+			return ERR_PTR(-ENOMEM);
+		newtlink->time = jiffies;
+		newtlink->tcon = ERR_PTR(-EACCES);
+		set_bit(TCON_LINK_PENDING, &newtlink->flags);
+
+		ret = radix_tree_preload(GFP_KERNEL);
+		if (ret != 0) {
+			kfree(newtlink);
+			return ERR_PTR(ret);
+		}
+
+		spin_lock(&cifs_sb->tcon_tree_lock);
+		tlink = radix_tree_lookup(&cifs_sb->tcon_tree, fsuid);
+		if (!tlink) {
+			tlink = newtlink;
+			newtlink = NULL;
+			ret = radix_tree_insert(&cifs_sb->tcon_tree, fsuid,
+						tlink);
+		}
+		spin_unlock(&cifs_sb->tcon_tree_lock);
+		radix_tree_preload_end();
+		kfree(newtlink);
+		if (ret)
+			return ERR_PTR(ret);
+	} else {
+		ret = wait_on_bit(&tlink->flags, TCON_LINK_PENDING,
+				  cifs_sb_tcon_pending_wait,
+				  TASK_INTERRUPTIBLE);
+		if (ret)
+			return ERR_PTR(ret);
+
+		spin_lock(&cifs_sb->tcon_tree_lock);
+		if (!IS_ERR(tlink->tcon) ||
+		    time_before(jiffies, tlink->time + TLINK_ERROR_EXPIRE)) {
+			spin_unlock(&cifs_sb->tcon_tree_lock);
+			return tlink->tcon;
+		}
+		set_bit(TCON_LINK_PENDING, &tlink->flags);
+		spin_unlock(&cifs_sb->tcon_tree_lock);
+	}
+
+	tlink->tcon = cifs_construct_tcon(cifs_sb, current_fsuid());
+	clear_bit(TCON_LINK_PENDING, &tlink->flags);
+	wake_up_bit(&tlink->flags, TCON_LINK_PENDING);
+	return tlink->tcon;
+}
-- 
1.6.6.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.