@@ -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;
@@ -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;
+}