aboutsummaryrefslogtreecommitdiffstats
path: root/fs/cifs
diff options
context:
space:
mode:
authorJeff Layton <jlayton@redhat.com>2010-10-06 19:51:11 -0400
committerSteve French <sfrench@us.ibm.com>2010-10-07 14:18:00 -0400
commit9d002df492b14c690425d9785530371b6c1ccbca (patch)
tree6ed1a52d0e348e985f7bd194d22ee6e7854fa9e8 /fs/cifs
parentc9928f7040a6e5f39e028bea500e0fde910d4a96 (diff)
cifs: add routines to build sessions and tcons on the fly
This patch is rather large, but it's a bit difficult to do piecemeal... For non-multiuser mounts, everything will basically work as it does today. A call to cifs_sb_tlink will return the "master" tcon link. 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@redhat.com> Signed-off-by: Steve French <sfrench@us.ibm.com>
Diffstat (limited to 'fs/cifs')
-rw-r--r--fs/cifs/cifs_fs_sb.h7
-rw-r--r--fs/cifs/cifsglob.h32
-rw-r--r--fs/cifs/connect.c268
3 files changed, 279 insertions, 28 deletions
diff --git a/fs/cifs/cifs_fs_sb.h b/fs/cifs/cifs_fs_sb.h
index e04e6923d354..5ce57bdf1865 100644
--- a/fs/cifs/cifs_fs_sb.h
+++ b/fs/cifs/cifs_fs_sb.h
@@ -15,6 +15,8 @@
15 * the GNU Lesser General Public License for more details. 15 * the GNU Lesser General Public License for more details.
16 * 16 *
17 */ 17 */
18#include <linux/radix-tree.h>
19
18#ifndef _CIFS_FS_SB_H 20#ifndef _CIFS_FS_SB_H
19#define _CIFS_FS_SB_H 21#define _CIFS_FS_SB_H
20 22
@@ -40,8 +42,9 @@
40#define CIFS_MOUNT_MULTIUSER 0x20000 /* multiuser mount */ 42#define CIFS_MOUNT_MULTIUSER 0x20000 /* multiuser mount */
41 43
42struct cifs_sb_info { 44struct cifs_sb_info {
43 struct cifsTconInfo *ptcon; /* primary mount */ 45 struct radix_tree_root tlink_tree;
44 struct list_head nested_tcon_q; 46#define CIFS_TLINK_MASTER_TAG 0 /* is "master" (mount) tcon */
47 spinlock_t tlink_tree_lock;
45 struct nls_table *local_nls; 48 struct nls_table *local_nls;
46 unsigned int rsize; 49 unsigned int rsize;
47 unsigned int wsize; 50 unsigned int wsize;
diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index d5324853203b..9a7c472a153f 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -317,42 +317,36 @@ struct cifsTconInfo {
317 * "get" on the container. 317 * "get" on the container.
318 */ 318 */
319struct tcon_link { 319struct tcon_link {
320 spinlock_t tl_lock; 320 unsigned long tl_index;
321 u32 tl_count; 321 unsigned long tl_flags;
322 u64 tl_time; 322#define TCON_LINK_MASTER 0
323#define TCON_LINK_PENDING 1
324#define TCON_LINK_IN_TREE 2
325 unsigned long tl_time;
326 atomic_t tl_count;
323 struct cifsTconInfo *tl_tcon; 327 struct cifsTconInfo *tl_tcon;
324}; 328};
325 329
326static inline struct tcon_link * 330extern struct tcon_link *cifs_sb_tlink(struct cifs_sb_info *cifs_sb);
327cifs_sb_tlink(struct cifs_sb_info *cifs_sb)
328{
329 return (struct tcon_link *)cifs_sb->ptcon;
330}
331 331
332static inline struct cifsTconInfo * 332static inline struct cifsTconInfo *
333tlink_tcon(struct tcon_link *tlink) 333tlink_tcon(struct tcon_link *tlink)
334{ 334{
335 return (struct cifsTconInfo *)tlink; 335 return tlink->tl_tcon;
336} 336}
337 337
338static inline void 338extern void cifs_put_tlink(struct tcon_link *tlink);
339cifs_put_tlink(struct tcon_link *tlink)
340{
341 return;
342}
343 339
344static inline struct tcon_link * 340static inline struct tcon_link *
345cifs_get_tlink(struct tcon_link *tlink) 341cifs_get_tlink(struct tcon_link *tlink)
346{ 342{
343 if (tlink && !IS_ERR(tlink))
344 atomic_inc(&tlink->tl_count);
347 return tlink; 345 return tlink;
348} 346}
349 347
350/* This function is always expected to succeed */ 348/* This function is always expected to succeed */
351static inline struct cifsTconInfo * 349extern struct cifsTconInfo *cifs_sb_master_tcon(struct cifs_sb_info *cifs_sb);
352cifs_sb_master_tcon(struct cifs_sb_info *cifs_sb)
353{
354 return cifs_sb->ptcon;
355}
356 350
357/* 351/*
358 * This info hangs off the cifsFileInfo structure, pointed to by llist. 352 * This info hangs off the cifsFileInfo structure, pointed to by llist.
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index f6a3091c2874..3156a9de947d 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -109,6 +109,9 @@ struct smb_vol {
109 struct nls_table *local_nls; 109 struct nls_table *local_nls;
110}; 110};
111 111
112#define TLINK_ERROR_EXPIRE (1 * HZ)
113
114
112static int ipv4_connect(struct TCP_Server_Info *server); 115static int ipv4_connect(struct TCP_Server_Info *server);
113static int ipv6_connect(struct TCP_Server_Info *server); 116static int ipv6_connect(struct TCP_Server_Info *server);
114 117
@@ -1959,6 +1962,23 @@ out_fail:
1959 return ERR_PTR(rc); 1962 return ERR_PTR(rc);
1960} 1963}
1961 1964
1965void
1966cifs_put_tlink(struct tcon_link *tlink)
1967{
1968 if (!tlink || IS_ERR(tlink))
1969 return;
1970
1971 if (!atomic_dec_and_test(&tlink->tl_count) ||
1972 test_bit(TCON_LINK_IN_TREE, &tlink->tl_flags)) {
1973 tlink->tl_time = jiffies;
1974 return;
1975 }
1976
1977 if (!IS_ERR(tlink_tcon(tlink)))
1978 cifs_put_tcon(tlink_tcon(tlink));
1979 kfree(tlink);
1980 return;
1981}
1962 1982
1963int 1983int
1964get_dfs_path(int xid, struct cifsSesInfo *pSesInfo, const char *old_path, 1984get_dfs_path(int xid, struct cifsSesInfo *pSesInfo, const char *old_path,
@@ -2641,6 +2661,7 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
2641 struct TCP_Server_Info *srvTcp; 2661 struct TCP_Server_Info *srvTcp;
2642 char *full_path; 2662 char *full_path;
2643 char *mount_data = mount_data_global; 2663 char *mount_data = mount_data_global;
2664 struct tcon_link *tlink;
2644#ifdef CONFIG_CIFS_DFS_UPCALL 2665#ifdef CONFIG_CIFS_DFS_UPCALL
2645 struct dfs_info3_param *referrals = NULL; 2666 struct dfs_info3_param *referrals = NULL;
2646 unsigned int num_referrals = 0; 2667 unsigned int num_referrals = 0;
@@ -2652,6 +2673,7 @@ try_mount_again:
2652 pSesInfo = NULL; 2673 pSesInfo = NULL;
2653 srvTcp = NULL; 2674 srvTcp = NULL;
2654 full_path = NULL; 2675 full_path = NULL;
2676 tlink = NULL;
2655 2677
2656 xid = GetXid(); 2678 xid = GetXid();
2657 2679
@@ -2727,8 +2749,6 @@ try_mount_again:
2727 goto remote_path_check; 2749 goto remote_path_check;
2728 } 2750 }
2729 2751
2730 cifs_sb->ptcon = tcon;
2731
2732 /* do not care if following two calls succeed - informational */ 2752 /* do not care if following two calls succeed - informational */
2733 if (!tcon->ipc) { 2753 if (!tcon->ipc) {
2734 CIFSSMBQFSDeviceInfo(xid, tcon); 2754 CIFSSMBQFSDeviceInfo(xid, tcon);
@@ -2837,6 +2857,35 @@ remote_path_check:
2837#endif 2857#endif
2838 } 2858 }
2839 2859
2860 if (rc)
2861 goto mount_fail_check;
2862
2863 /* now, hang the tcon off of the superblock */
2864 tlink = kzalloc(sizeof *tlink, GFP_KERNEL);
2865 if (tlink == NULL) {
2866 rc = -ENOMEM;
2867 goto mount_fail_check;
2868 }
2869
2870 tlink->tl_index = pSesInfo->linux_uid;
2871 tlink->tl_tcon = tcon;
2872 tlink->tl_time = jiffies;
2873 set_bit(TCON_LINK_MASTER, &tlink->tl_flags);
2874 set_bit(TCON_LINK_IN_TREE, &tlink->tl_flags);
2875
2876 rc = radix_tree_preload(GFP_KERNEL);
2877 if (rc == -ENOMEM) {
2878 kfree(tlink);
2879 goto mount_fail_check;
2880 }
2881
2882 spin_lock(&cifs_sb->tlink_tree_lock);
2883 radix_tree_insert(&cifs_sb->tlink_tree, pSesInfo->linux_uid, tlink);
2884 radix_tree_tag_set(&cifs_sb->tlink_tree, pSesInfo->linux_uid,
2885 CIFS_TLINK_MASTER_TAG);
2886 spin_unlock(&cifs_sb->tlink_tree_lock);
2887 radix_tree_preload_end();
2888
2840mount_fail_check: 2889mount_fail_check:
2841 /* on error free sesinfo and tcon struct if needed */ 2890 /* on error free sesinfo and tcon struct if needed */
2842 if (rc) { 2891 if (rc) {
@@ -3023,19 +3072,37 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,
3023int 3072int
3024cifs_umount(struct super_block *sb, struct cifs_sb_info *cifs_sb) 3073cifs_umount(struct super_block *sb, struct cifs_sb_info *cifs_sb)
3025{ 3074{
3026 int rc = 0; 3075 int i, ret;
3027 char *tmp; 3076 char *tmp;
3028 struct cifsTconInfo *tcon = cifs_sb_master_tcon(cifs_sb); 3077 struct tcon_link *tlink[8];
3078 unsigned long index = 0;
3079
3080 do {
3081 spin_lock(&cifs_sb->tlink_tree_lock);
3082 ret = radix_tree_gang_lookup(&cifs_sb->tlink_tree,
3083 (void **)tlink, index,
3084 ARRAY_SIZE(tlink));
3085 /* increment index for next pass */
3086 if (ret > 0)
3087 index = tlink[ret - 1]->tl_index + 1;
3088 for (i = 0; i < ret; i++) {
3089 cifs_get_tlink(tlink[i]);
3090 clear_bit(TCON_LINK_IN_TREE, &tlink[i]->tl_flags);
3091 radix_tree_delete(&cifs_sb->tlink_tree,
3092 tlink[i]->tl_index);
3093 }
3094 spin_unlock(&cifs_sb->tlink_tree_lock);
3029 3095
3030 cifs_put_tcon(tcon); 3096 for (i = 0; i < ret; i++)
3097 cifs_put_tlink(tlink[i]);
3098 } while (ret != 0);
3031 3099
3032 cifs_sb->ptcon = NULL;
3033 tmp = cifs_sb->prepath; 3100 tmp = cifs_sb->prepath;
3034 cifs_sb->prepathlen = 0; 3101 cifs_sb->prepathlen = 0;
3035 cifs_sb->prepath = NULL; 3102 cifs_sb->prepath = NULL;
3036 kfree(tmp); 3103 kfree(tmp);
3037 3104
3038 return rc; 3105 return 0;
3039} 3106}
3040 3107
3041int cifs_negotiate_protocol(unsigned int xid, struct cifsSesInfo *ses) 3108int cifs_negotiate_protocol(unsigned int xid, struct cifsSesInfo *ses)
@@ -3096,3 +3163,190 @@ int cifs_setup_session(unsigned int xid, struct cifsSesInfo *ses,
3096 return rc; 3163 return rc;
3097} 3164}
3098 3165
3166struct cifsTconInfo *
3167cifs_construct_tcon(struct cifs_sb_info *cifs_sb, uid_t fsuid)
3168{
3169 struct cifsTconInfo *master_tcon = cifs_sb_master_tcon(cifs_sb);
3170 struct cifsSesInfo *ses;
3171 struct cifsTconInfo *tcon = NULL;
3172 struct smb_vol *vol_info;
3173 char username[MAX_USERNAME_SIZE + 1];
3174
3175 vol_info = kzalloc(sizeof(*vol_info), GFP_KERNEL);
3176 if (vol_info == NULL) {
3177 tcon = ERR_PTR(-ENOMEM);
3178 goto out;
3179 }
3180
3181 snprintf(username, MAX_USERNAME_SIZE, "krb50x%x", fsuid);
3182 vol_info->username = username;
3183 vol_info->local_nls = cifs_sb->local_nls;
3184 vol_info->linux_uid = fsuid;
3185 vol_info->cred_uid = fsuid;
3186 vol_info->UNC = master_tcon->treeName;
3187 vol_info->retry = master_tcon->retry;
3188 vol_info->nocase = master_tcon->nocase;
3189 vol_info->local_lease = master_tcon->local_lease;
3190 vol_info->no_linux_ext = !master_tcon->unix_ext;
3191
3192 /* FIXME: allow for other secFlg settings */
3193 vol_info->secFlg = CIFSSEC_MUST_KRB5;
3194
3195 /* get a reference for the same TCP session */
3196 write_lock(&cifs_tcp_ses_lock);
3197 ++master_tcon->ses->server->srv_count;
3198 write_unlock(&cifs_tcp_ses_lock);
3199
3200 ses = cifs_get_smb_ses(master_tcon->ses->server, vol_info);
3201 if (IS_ERR(ses)) {
3202 tcon = (struct cifsTconInfo *)ses;
3203 cifs_put_tcp_session(master_tcon->ses->server);
3204 goto out;
3205 }
3206
3207 tcon = cifs_get_tcon(ses, vol_info);
3208 if (IS_ERR(tcon)) {
3209 cifs_put_smb_ses(ses);
3210 goto out;
3211 }
3212
3213 if (ses->capabilities & CAP_UNIX)
3214 reset_cifs_unix_caps(0, tcon, NULL, vol_info);
3215out:
3216 kfree(vol_info);
3217
3218 return tcon;
3219}
3220
3221static struct tcon_link *
3222cifs_sb_master_tlink(struct cifs_sb_info *cifs_sb)
3223{
3224 struct tcon_link *tlink;
3225 unsigned int ret;
3226
3227 spin_lock(&cifs_sb->tlink_tree_lock);
3228 ret = radix_tree_gang_lookup_tag(&cifs_sb->tlink_tree, (void **)&tlink,
3229 0, 1, CIFS_TLINK_MASTER_TAG);
3230 spin_unlock(&cifs_sb->tlink_tree_lock);
3231
3232 /* the master tcon should always be present */
3233 if (ret == 0)
3234 BUG();
3235
3236 return tlink;
3237}
3238
3239struct cifsTconInfo *
3240cifs_sb_master_tcon(struct cifs_sb_info *cifs_sb)
3241{
3242 return tlink_tcon(cifs_sb_master_tlink(cifs_sb));
3243}
3244
3245static int
3246cifs_sb_tcon_pending_wait(void *unused)
3247{
3248 schedule();
3249 return signal_pending(current) ? -ERESTARTSYS : 0;
3250}
3251
3252/*
3253 * Find or construct an appropriate tcon given a cifs_sb and the fsuid of the
3254 * current task.
3255 *
3256 * If the superblock doesn't refer to a multiuser mount, then just return
3257 * the master tcon for the mount.
3258 *
3259 * First, search the radix tree for an existing tcon for this fsuid. If one
3260 * exists, then check to see if it's pending construction. If it is then wait
3261 * for construction to complete. Once it's no longer pending, check to see if
3262 * it failed and either return an error or retry construction, depending on
3263 * the timeout.
3264 *
3265 * If one doesn't exist then insert a new tcon_link struct into the tree and
3266 * try to construct a new one.
3267 */
3268struct tcon_link *
3269cifs_sb_tlink(struct cifs_sb_info *cifs_sb)
3270{
3271 int ret;
3272 unsigned long fsuid = (unsigned long) current_fsuid();
3273 struct tcon_link *tlink, *newtlink;
3274
3275 if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MULTIUSER))
3276 return cifs_get_tlink(cifs_sb_master_tlink(cifs_sb));
3277
3278 spin_lock(&cifs_sb->tlink_tree_lock);
3279 tlink = radix_tree_lookup(&cifs_sb->tlink_tree, fsuid);
3280 if (tlink)
3281 cifs_get_tlink(tlink);
3282 spin_unlock(&cifs_sb->tlink_tree_lock);
3283
3284 if (tlink == NULL) {
3285 newtlink = kzalloc(sizeof(*tlink), GFP_KERNEL);
3286 if (newtlink == NULL)
3287 return ERR_PTR(-ENOMEM);
3288 newtlink->tl_index = fsuid;
3289 newtlink->tl_tcon = ERR_PTR(-EACCES);
3290 set_bit(TCON_LINK_PENDING, &newtlink->tl_flags);
3291 set_bit(TCON_LINK_IN_TREE, &newtlink->tl_flags);
3292 cifs_get_tlink(newtlink);
3293
3294 ret = radix_tree_preload(GFP_KERNEL);
3295 if (ret != 0) {
3296 kfree(newtlink);
3297 return ERR_PTR(ret);
3298 }
3299
3300 spin_lock(&cifs_sb->tlink_tree_lock);
3301 /* was one inserted after previous search? */
3302 tlink = radix_tree_lookup(&cifs_sb->tlink_tree, fsuid);
3303 if (tlink) {
3304 cifs_get_tlink(tlink);
3305 spin_unlock(&cifs_sb->tlink_tree_lock);
3306 radix_tree_preload_end();
3307 kfree(newtlink);
3308 goto wait_for_construction;
3309 }
3310 ret = radix_tree_insert(&cifs_sb->tlink_tree, fsuid, newtlink);
3311 spin_unlock(&cifs_sb->tlink_tree_lock);
3312 radix_tree_preload_end();
3313 if (ret) {
3314 kfree(newtlink);
3315 return ERR_PTR(ret);
3316 }
3317 tlink = newtlink;
3318 } else {
3319wait_for_construction:
3320 ret = wait_on_bit(&tlink->tl_flags, TCON_LINK_PENDING,
3321 cifs_sb_tcon_pending_wait,
3322 TASK_INTERRUPTIBLE);
3323 if (ret) {
3324 cifs_put_tlink(tlink);
3325 return ERR_PTR(ret);
3326 }
3327
3328 /* if it's good, return it */
3329 if (!IS_ERR(tlink->tl_tcon))
3330 return tlink;
3331
3332 /* return error if we tried this already recently */
3333 if (time_before(jiffies, tlink->tl_time + TLINK_ERROR_EXPIRE)) {
3334 cifs_put_tlink(tlink);
3335 return ERR_PTR(-EACCES);
3336 }
3337
3338 if (test_and_set_bit(TCON_LINK_PENDING, &tlink->tl_flags))
3339 goto wait_for_construction;
3340 }
3341
3342 tlink->tl_tcon = cifs_construct_tcon(cifs_sb, fsuid);
3343 clear_bit(TCON_LINK_PENDING, &tlink->tl_flags);
3344 wake_up_bit(&tlink->tl_flags, TCON_LINK_PENDING);
3345
3346 if (IS_ERR(tlink->tl_tcon)) {
3347 cifs_put_tlink(tlink);
3348 return ERR_PTR(-EACCES);
3349 }
3350
3351 return tlink;
3352}