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 <jlayton@xxxxxxxxxx> --- 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 -- To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html