diff options
Diffstat (limited to 'fs/cifs/connect.c')
| -rw-r--r-- | fs/cifs/connect.c | 4423 | 
1 files changed, 2544 insertions, 1879 deletions
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 251a17c0354..20d75b8ddb2 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -1,7 +1,7 @@  /*   *   fs/cifs/connect.c   * - *   Copyright (C) International Business Machines  Corp., 2002,2009 + *   Copyright (C) International Business Machines  Corp., 2002,2011   *   Author(s): Steve French (sfrench@us.ibm.com)   *   *   This library is free software; you can redistribute it and/or modify @@ -37,7 +37,11 @@  #include <asm/uaccess.h>  #include <asm/processor.h>  #include <linux/inet.h> +#include <linux/module.h> +#include <keys/user-type.h>  #include <net/ipv6.h> +#include <linux/parser.h> +  #include "cifspdu.h"  #include "cifsglob.h"  #include "cifsproto.h" @@ -52,72 +56,235 @@  #define CIFS_PORT 445  #define RFC1001_PORT 139 -extern void SMBNTencrypt(unsigned char *passwd, unsigned char *c8, -			 unsigned char *p24); -  extern mempool_t *cifs_req_poolp; -struct smb_vol { -	char *username; -	char *password; -	char *domainname; -	char *UNC; -	char *UNCip; -	char *iocharset;  /* local code page for mapping to and from Unicode */ -	char source_rfc1001_name[16]; /* netbios name of client */ -	char target_rfc1001_name[16]; /* netbios name of server for Win9x/ME */ -	uid_t cred_uid; -	uid_t linux_uid; -	gid_t linux_gid; -	mode_t file_mode; -	mode_t dir_mode; -	unsigned secFlg; -	bool retry:1; -	bool intr:1; -	bool setuids:1; -	bool override_uid:1; -	bool override_gid:1; -	bool dynperm:1; -	bool noperm:1; -	bool no_psx_acl:1; /* set if posix acl support should be disabled */ -	bool cifs_acl:1; -	bool no_xattr:1;   /* set if xattr (EA) support should be disabled*/ -	bool server_ino:1; /* use inode numbers from server ie UniqueId */ -	bool direct_io:1; -	bool remap:1;      /* set to remap seven reserved chars in filenames */ -	bool posix_paths:1; /* unset to not ask for posix pathnames. */ -	bool no_linux_ext:1; -	bool sfu_emul:1; -	bool nullauth:1;   /* attempt to authenticate with null user */ -	bool nocase:1;     /* request case insensitive filenames */ -	bool nobrl:1;      /* disable sending byte range locks to srv */ -	bool mand_lock:1;  /* send mandatory not posix byte range lock reqs */ -	bool seal:1;       /* request transport encryption on share */ -	bool nodfs:1;      /* Do not request DFS, even if available */ -	bool local_lease:1; /* check leases only on local system, not remote */ -	bool noblocksnd:1; -	bool noautotune:1; -	bool nostrictsync:1; /* do not force expensive SMBflush on every sync */ -	bool fsc:1;	/* enable fscache */ -	bool mfsymlinks:1; /* use Minshall+French Symlinks */ -	bool multiuser:1; -	unsigned int rsize; -	unsigned int wsize; -	bool sockopt_tcp_nodelay:1; -	unsigned short int port; -	char *prepath; -	struct sockaddr_storage srcaddr; /* allow binding to a local IP */ -	struct nls_table *local_nls; -}; -  /* FIXME: should these be tunable? */  #define TLINK_ERROR_EXPIRE	(1 * HZ)  #define TLINK_IDLE_EXPIRE	(600 * HZ) -static int ipv4_connect(struct TCP_Server_Info *server); -static int ipv6_connect(struct TCP_Server_Info *server); +enum { + +	/* Mount options that take no arguments */ +	Opt_user_xattr, Opt_nouser_xattr, +	Opt_forceuid, Opt_noforceuid, +	Opt_forcegid, Opt_noforcegid, +	Opt_noblocksend, Opt_noautotune, +	Opt_hard, Opt_soft, Opt_perm, Opt_noperm, +	Opt_mapchars, Opt_nomapchars, Opt_sfu, +	Opt_nosfu, Opt_nodfs, Opt_posixpaths, +	Opt_noposixpaths, Opt_nounix, +	Opt_nocase, +	Opt_brl, Opt_nobrl, +	Opt_forcemandatorylock, Opt_setuids, +	Opt_nosetuids, Opt_dynperm, Opt_nodynperm, +	Opt_nohard, Opt_nosoft, +	Opt_nointr, Opt_intr, +	Opt_nostrictsync, Opt_strictsync, +	Opt_serverino, Opt_noserverino, +	Opt_rwpidforward, Opt_cifsacl, Opt_nocifsacl, +	Opt_acl, Opt_noacl, Opt_locallease, +	Opt_sign, Opt_seal, Opt_noac, +	Opt_fsc, Opt_mfsymlinks, +	Opt_multiuser, Opt_sloppy, Opt_nosharesock, + +	/* Mount options which take numeric value */ +	Opt_backupuid, Opt_backupgid, Opt_uid, +	Opt_cruid, Opt_gid, Opt_file_mode, +	Opt_dirmode, Opt_port, +	Opt_rsize, Opt_wsize, Opt_actimeo, + +	/* Mount options which take string value */ +	Opt_user, Opt_pass, Opt_ip, +	Opt_domain, Opt_srcaddr, Opt_iocharset, +	Opt_netbiosname, Opt_servern, +	Opt_ver, Opt_vers, Opt_sec, Opt_cache, + +	/* Mount options to be ignored */ +	Opt_ignore, + +	/* Options which could be blank */ +	Opt_blank_pass, +	Opt_blank_user, +	Opt_blank_ip, + +	Opt_err +}; + +static const match_table_t cifs_mount_option_tokens = { + +	{ Opt_user_xattr, "user_xattr" }, +	{ Opt_nouser_xattr, "nouser_xattr" }, +	{ Opt_forceuid, "forceuid" }, +	{ Opt_noforceuid, "noforceuid" }, +	{ Opt_forcegid, "forcegid" }, +	{ Opt_noforcegid, "noforcegid" }, +	{ Opt_noblocksend, "noblocksend" }, +	{ Opt_noautotune, "noautotune" }, +	{ Opt_hard, "hard" }, +	{ Opt_soft, "soft" }, +	{ Opt_perm, "perm" }, +	{ Opt_noperm, "noperm" }, +	{ Opt_mapchars, "mapchars" }, +	{ Opt_nomapchars, "nomapchars" }, +	{ Opt_sfu, "sfu" }, +	{ Opt_nosfu, "nosfu" }, +	{ Opt_nodfs, "nodfs" }, +	{ Opt_posixpaths, "posixpaths" }, +	{ Opt_noposixpaths, "noposixpaths" }, +	{ Opt_nounix, "nounix" }, +	{ Opt_nounix, "nolinux" }, +	{ Opt_nocase, "nocase" }, +	{ Opt_nocase, "ignorecase" }, +	{ Opt_brl, "brl" }, +	{ Opt_nobrl, "nobrl" }, +	{ Opt_nobrl, "nolock" }, +	{ Opt_forcemandatorylock, "forcemandatorylock" }, +	{ Opt_forcemandatorylock, "forcemand" }, +	{ Opt_setuids, "setuids" }, +	{ Opt_nosetuids, "nosetuids" }, +	{ Opt_dynperm, "dynperm" }, +	{ Opt_nodynperm, "nodynperm" }, +	{ Opt_nohard, "nohard" }, +	{ Opt_nosoft, "nosoft" }, +	{ Opt_nointr, "nointr" }, +	{ Opt_intr, "intr" }, +	{ Opt_nostrictsync, "nostrictsync" }, +	{ Opt_strictsync, "strictsync" }, +	{ Opt_serverino, "serverino" }, +	{ Opt_noserverino, "noserverino" }, +	{ Opt_rwpidforward, "rwpidforward" }, +	{ Opt_cifsacl, "cifsacl" }, +	{ Opt_nocifsacl, "nocifsacl" }, +	{ Opt_acl, "acl" }, +	{ Opt_noacl, "noacl" }, +	{ Opt_locallease, "locallease" }, +	{ Opt_sign, "sign" }, +	{ Opt_seal, "seal" }, +	{ Opt_noac, "noac" }, +	{ Opt_fsc, "fsc" }, +	{ Opt_mfsymlinks, "mfsymlinks" }, +	{ Opt_multiuser, "multiuser" }, +	{ Opt_sloppy, "sloppy" }, +	{ Opt_nosharesock, "nosharesock" }, + +	{ Opt_backupuid, "backupuid=%s" }, +	{ Opt_backupgid, "backupgid=%s" }, +	{ Opt_uid, "uid=%s" }, +	{ Opt_cruid, "cruid=%s" }, +	{ Opt_gid, "gid=%s" }, +	{ Opt_file_mode, "file_mode=%s" }, +	{ Opt_dirmode, "dirmode=%s" }, +	{ Opt_dirmode, "dir_mode=%s" }, +	{ Opt_port, "port=%s" }, +	{ Opt_rsize, "rsize=%s" }, +	{ Opt_wsize, "wsize=%s" }, +	{ Opt_actimeo, "actimeo=%s" }, + +	{ Opt_blank_user, "user=" }, +	{ Opt_blank_user, "username=" }, +	{ Opt_user, "user=%s" }, +	{ Opt_user, "username=%s" }, +	{ Opt_blank_pass, "pass=" }, +	{ Opt_blank_pass, "password=" }, +	{ Opt_pass, "pass=%s" }, +	{ Opt_pass, "password=%s" }, +	{ Opt_blank_ip, "ip=" }, +	{ Opt_blank_ip, "addr=" }, +	{ Opt_ip, "ip=%s" }, +	{ Opt_ip, "addr=%s" }, +	{ Opt_ignore, "unc=%s" }, +	{ Opt_ignore, "target=%s" }, +	{ Opt_ignore, "path=%s" }, +	{ Opt_domain, "dom=%s" }, +	{ Opt_domain, "domain=%s" }, +	{ Opt_domain, "workgroup=%s" }, +	{ Opt_srcaddr, "srcaddr=%s" }, +	{ Opt_ignore, "prefixpath=%s" }, +	{ Opt_iocharset, "iocharset=%s" }, +	{ Opt_netbiosname, "netbiosname=%s" }, +	{ Opt_servern, "servern=%s" }, +	{ Opt_ver, "ver=%s" }, +	{ Opt_vers, "vers=%s" }, +	{ Opt_sec, "sec=%s" }, +	{ Opt_cache, "cache=%s" }, + +	{ Opt_ignore, "cred" }, +	{ Opt_ignore, "credentials" }, +	{ Opt_ignore, "cred=%s" }, +	{ Opt_ignore, "credentials=%s" }, +	{ Opt_ignore, "guest" }, +	{ Opt_ignore, "rw" }, +	{ Opt_ignore, "ro" }, +	{ Opt_ignore, "suid" }, +	{ Opt_ignore, "nosuid" }, +	{ Opt_ignore, "exec" }, +	{ Opt_ignore, "noexec" }, +	{ Opt_ignore, "nodev" }, +	{ Opt_ignore, "noauto" }, +	{ Opt_ignore, "dev" }, +	{ Opt_ignore, "mand" }, +	{ Opt_ignore, "nomand" }, +	{ Opt_ignore, "_netdev" }, + +	{ Opt_err, NULL } +}; + +enum { +	Opt_sec_krb5, Opt_sec_krb5i, Opt_sec_krb5p, +	Opt_sec_ntlmsspi, Opt_sec_ntlmssp, +	Opt_ntlm, Opt_sec_ntlmi, Opt_sec_ntlmv2, +	Opt_sec_ntlmv2i, Opt_sec_lanman, +	Opt_sec_none, + +	Opt_sec_err +}; + +static const match_table_t cifs_secflavor_tokens = { +	{ Opt_sec_krb5, "krb5" }, +	{ Opt_sec_krb5i, "krb5i" }, +	{ Opt_sec_krb5p, "krb5p" }, +	{ Opt_sec_ntlmsspi, "ntlmsspi" }, +	{ Opt_sec_ntlmssp, "ntlmssp" }, +	{ Opt_ntlm, "ntlm" }, +	{ Opt_sec_ntlmi, "ntlmi" }, +	{ Opt_sec_ntlmv2, "nontlm" }, +	{ Opt_sec_ntlmv2, "ntlmv2" }, +	{ Opt_sec_ntlmv2i, "ntlmv2i" }, +	{ Opt_sec_lanman, "lanman" }, +	{ Opt_sec_none, "none" }, + +	{ Opt_sec_err, NULL } +}; + +/* cache flavors */ +enum { +	Opt_cache_loose, +	Opt_cache_strict, +	Opt_cache_none, +	Opt_cache_err +}; + +static const match_table_t cifs_cacheflavor_tokens = { +	{ Opt_cache_loose, "loose" }, +	{ Opt_cache_strict, "strict" }, +	{ Opt_cache_none, "none" }, +	{ Opt_cache_err, NULL } +}; + +static const match_table_t cifs_smb_version_tokens = { +	{ Smb_1, SMB1_VERSION_STRING }, +	{ Smb_20, SMB20_VERSION_STRING}, +	{ Smb_21, SMB21_VERSION_STRING }, +	{ Smb_30, SMB30_VERSION_STRING }, +	{ Smb_302, SMB302_VERSION_STRING }, +}; + +static int ip_connect(struct TCP_Server_Info *server); +static int generic_ip_connect(struct TCP_Server_Info *server);  static void tlink_rb_insert(struct rb_root *root, struct tcon_link *new_tlink);  static void cifs_prune_tlinks(struct work_struct *work); +static int cifs_setup_volume_info(struct smb_vol *volume_info, char *mount_data, +					const char *devname);  /*   * cifs tcp session reconnection @@ -127,14 +294,15 @@ static void cifs_prune_tlinks(struct work_struct *work);   * reconnect tcp session   * wake up waiters on reconnection? - (not needed currently)   */ -static int +int  cifs_reconnect(struct TCP_Server_Info *server)  {  	int rc = 0;  	struct list_head *tmp, *tmp2; -	struct cifsSesInfo *ses; -	struct cifsTconInfo *tcon; +	struct cifs_ses *ses; +	struct cifs_tcon *tcon;  	struct mid_q_entry *mid_entry; +	struct list_head retry_list;  	spin_lock(&GlobalMid_Lock);  	if (server->tcpStatus == CifsExiting) { @@ -146,31 +314,37 @@ cifs_reconnect(struct TCP_Server_Info *server)  		server->tcpStatus = CifsNeedReconnect;  	spin_unlock(&GlobalMid_Lock);  	server->maxBuf = 0; +#ifdef CONFIG_CIFS_SMB2 +	server->max_read = 0; +#endif -	cFYI(1, "Reconnecting tcp session"); +	cifs_dbg(FYI, "Reconnecting tcp session\n");  	/* before reconnecting the tcp session, mark the smb session (uid)  		and the tid bad so they are not used until reconnected */ +	cifs_dbg(FYI, "%s: marking sessions and tcons for reconnect\n", +		 __func__);  	spin_lock(&cifs_tcp_ses_lock);  	list_for_each(tmp, &server->smb_ses_list) { -		ses = list_entry(tmp, struct cifsSesInfo, smb_ses_list); +		ses = list_entry(tmp, struct cifs_ses, smb_ses_list);  		ses->need_reconnect = true;  		ses->ipc_tid = 0;  		list_for_each(tmp2, &ses->tcon_list) { -			tcon = list_entry(tmp2, struct cifsTconInfo, tcon_list); +			tcon = list_entry(tmp2, struct cifs_tcon, tcon_list);  			tcon->need_reconnect = true;  		}  	}  	spin_unlock(&cifs_tcp_ses_lock); +  	/* do not want to be sending data on a socket we are freeing */ +	cifs_dbg(FYI, "%s: tearing down socket\n", __func__);  	mutex_lock(&server->srv_mutex);  	if (server->ssocket) { -		cFYI(1, "State: 0x%x Flags: 0x%lx", server->ssocket->state, -			server->ssocket->flags); +		cifs_dbg(FYI, "State: 0x%x Flags: 0x%lx\n", +			 server->ssocket->state, server->ssocket->flags);  		kernel_sock_shutdown(server->ssocket, SHUT_WR); -		cFYI(1, "Post shutdown state: 0x%x Flags: 0x%lx", -			server->ssocket->state, -			server->ssocket->flags); +		cifs_dbg(FYI, "Post shutdown state: 0x%x Flags: 0x%lx\n", +			 server->ssocket->state, server->ssocket->flags);  		sock_release(server->ssocket);  		server->ssocket = NULL;  	} @@ -179,466 +353,342 @@ cifs_reconnect(struct TCP_Server_Info *server)  	kfree(server->session_key.response);  	server->session_key.response = NULL;  	server->session_key.len = 0; +	server->lstrp = jiffies; +	mutex_unlock(&server->srv_mutex); +	/* mark submitted MIDs for retry and issue callback */ +	INIT_LIST_HEAD(&retry_list); +	cifs_dbg(FYI, "%s: moving mids to private list\n", __func__);  	spin_lock(&GlobalMid_Lock); -	list_for_each(tmp, &server->pending_mid_q) { -		mid_entry = list_entry(tmp, struct -					mid_q_entry, -					qhead); -		if (mid_entry->midState == MID_REQUEST_SUBMITTED) { -				/* Mark other intransit requests as needing -				   retry so we do not immediately mark the -				   session bad again (ie after we reconnect -				   below) as they timeout too */ -			mid_entry->midState = MID_RETRY_NEEDED; -		} +	list_for_each_safe(tmp, tmp2, &server->pending_mid_q) { +		mid_entry = list_entry(tmp, struct mid_q_entry, qhead); +		if (mid_entry->mid_state == MID_REQUEST_SUBMITTED) +			mid_entry->mid_state = MID_RETRY_NEEDED; +		list_move(&mid_entry->qhead, &retry_list);  	}  	spin_unlock(&GlobalMid_Lock); -	mutex_unlock(&server->srv_mutex); -	while ((server->tcpStatus != CifsExiting) && -	       (server->tcpStatus != CifsGood)) { +	cifs_dbg(FYI, "%s: issuing mid callbacks\n", __func__); +	list_for_each_safe(tmp, tmp2, &retry_list) { +		mid_entry = list_entry(tmp, struct mid_q_entry, qhead); +		list_del_init(&mid_entry->qhead); +		mid_entry->callback(mid_entry); +	} + +	do {  		try_to_freeze(); -		if (server->addr.sockAddr6.sin6_family == AF_INET6) -			rc = ipv6_connect(server); -		else -			rc = ipv4_connect(server); + +		/* we should try only the port we connected to before */ +		mutex_lock(&server->srv_mutex); +		rc = generic_ip_connect(server);  		if (rc) { -			cFYI(1, "reconnect error %d", rc); +			cifs_dbg(FYI, "reconnect error %d\n", rc);  			msleep(3000);  		} else {  			atomic_inc(&tcpSesReconnectCount);  			spin_lock(&GlobalMid_Lock);  			if (server->tcpStatus != CifsExiting) -				server->tcpStatus = CifsGood; +				server->tcpStatus = CifsNeedNegotiate;  			spin_unlock(&GlobalMid_Lock); -	/*		atomic_set(&server->inFlight,0);*/ -			wake_up(&server->response_q);  		} -	} +		mutex_unlock(&server->srv_mutex); +	} while (server->tcpStatus == CifsNeedReconnect); +  	return rc;  } -/* -	return codes: -		0 	not a transact2, or all data present -		>0 	transact2 with that much data missing -		-EINVAL = invalid transact2 - - */ -static int check2ndT2(struct smb_hdr *pSMB, unsigned int maxBufSize) +static void +cifs_echo_request(struct work_struct *work)  { -	struct smb_t2_rsp *pSMBt; -	int total_data_size; -	int data_in_this_rsp; -	int remaining; - -	if (pSMB->Command != SMB_COM_TRANSACTION2) -		return 0; +	int rc; +	struct TCP_Server_Info *server = container_of(work, +					struct TCP_Server_Info, echo.work); -	/* check for plausible wct, bcc and t2 data and parm sizes */ -	/* check for parm and data offset going beyond end of smb */ -	if (pSMB->WordCount != 10) { /* coalesce_t2 depends on this */ -		cFYI(1, "invalid transact2 word count"); -		return -EINVAL; -	} +	/* +	 * We cannot send an echo if it is disabled or until the +	 * NEGOTIATE_PROTOCOL request is done, which is indicated by +	 * server->ops->need_neg() == true. Also, no need to ping if +	 * we got a response recently. +	 */ +	if (!server->ops->need_neg || server->ops->need_neg(server) || +	    (server->ops->can_echo && !server->ops->can_echo(server)) || +	    time_before(jiffies, server->lstrp + SMB_ECHO_INTERVAL - HZ)) +		goto requeue_echo; -	pSMBt = (struct smb_t2_rsp *)pSMB; +	rc = server->ops->echo ? server->ops->echo(server) : -ENOSYS; +	if (rc) +		cifs_dbg(FYI, "Unable to send echo request to server: %s\n", +			 server->hostname); -	total_data_size = le16_to_cpu(pSMBt->t2_rsp.TotalDataCount); -	data_in_this_rsp = le16_to_cpu(pSMBt->t2_rsp.DataCount); +requeue_echo: +	queue_delayed_work(cifsiod_wq, &server->echo, SMB_ECHO_INTERVAL); +} -	remaining = total_data_size - data_in_this_rsp; +static bool +allocate_buffers(struct TCP_Server_Info *server) +{ +	if (!server->bigbuf) { +		server->bigbuf = (char *)cifs_buf_get(); +		if (!server->bigbuf) { +			cifs_dbg(VFS, "No memory for large SMB response\n"); +			msleep(3000); +			/* retry will check if exiting */ +			return false; +		} +	} else if (server->large_buf) { +		/* we are reusing a dirty large buf, clear its start */ +		memset(server->bigbuf, 0, HEADER_SIZE(server)); +	} -	if (remaining == 0) -		return 0; -	else if (remaining < 0) { -		cFYI(1, "total data %d smaller than data in frame %d", -			total_data_size, data_in_this_rsp); -		return -EINVAL; -	} else { -		cFYI(1, "missing %d bytes from transact2, check next response", -			remaining); -		if (total_data_size > maxBufSize) { -			cERROR(1, "TotalDataSize %d is over maximum buffer %d", -				total_data_size, maxBufSize); -			return -EINVAL; +	if (!server->smallbuf) { +		server->smallbuf = (char *)cifs_small_buf_get(); +		if (!server->smallbuf) { +			cifs_dbg(VFS, "No memory for SMB response\n"); +			msleep(1000); +			/* retry will check if exiting */ +			return false;  		} -		return remaining; +		/* beginning of smb buffer is cleared in our buf_get */ +	} else { +		/* if existing small buf clear beginning */ +		memset(server->smallbuf, 0, HEADER_SIZE(server));  	} + +	return true;  } -static int coalesce_t2(struct smb_hdr *psecond, struct smb_hdr *pTargetSMB) +static bool +server_unresponsive(struct TCP_Server_Info *server)  { -	struct smb_t2_rsp *pSMB2 = (struct smb_t2_rsp *)psecond; -	struct smb_t2_rsp *pSMBt  = (struct smb_t2_rsp *)pTargetSMB; -	int total_data_size; -	int total_in_buf; -	int remaining; -	int total_in_buf2; -	char *data_area_of_target; -	char *data_area_of_buf2; -	__u16 byte_count; - -	total_data_size = le16_to_cpu(pSMBt->t2_rsp.TotalDataCount); - -	if (total_data_size != le16_to_cpu(pSMB2->t2_rsp.TotalDataCount)) { -		cFYI(1, "total data size of primary and secondary t2 differ"); +	/* +	 * We need to wait 2 echo intervals to make sure we handle such +	 * situations right: +	 * 1s  client sends a normal SMB request +	 * 2s  client gets a response +	 * 30s echo workqueue job pops, and decides we got a response recently +	 *     and don't need to send another +	 * ... +	 * 65s kernel_recvmsg times out, and we see that we haven't gotten +	 *     a response in >60s. +	 */ +	if (server->tcpStatus == CifsGood && +	    time_after(jiffies, server->lstrp + 2 * SMB_ECHO_INTERVAL)) { +		cifs_dbg(VFS, "Server %s has not responded in %d seconds. Reconnecting...\n", +			 server->hostname, (2 * SMB_ECHO_INTERVAL) / HZ); +		cifs_reconnect(server); +		wake_up(&server->response_q); +		return true;  	} -	total_in_buf = le16_to_cpu(pSMBt->t2_rsp.DataCount); - -	remaining = total_data_size - total_in_buf; +	return false; +} -	if (remaining < 0) -		return -EINVAL; +/* + * kvec_array_init - clone a kvec array, and advance into it + * @new:	pointer to memory for cloned array + * @iov:	pointer to original array + * @nr_segs:	number of members in original array + * @bytes:	number of bytes to advance into the cloned array + * + * This function will copy the array provided in iov to a section of memory + * and advance the specified number of bytes into the new array. It returns + * the number of segments in the new array. "new" must be at least as big as + * the original iov array. + */ +static unsigned int +kvec_array_init(struct kvec *new, struct kvec *iov, unsigned int nr_segs, +		size_t bytes) +{ +	size_t base = 0; -	if (remaining == 0) /* nothing to do, ignore */ -		return 0; +	while (bytes || !iov->iov_len) { +		int copy = min(bytes, iov->iov_len); -	total_in_buf2 = le16_to_cpu(pSMB2->t2_rsp.DataCount); -	if (remaining < total_in_buf2) { -		cFYI(1, "transact2 2nd response contains too much data"); +		bytes -= copy; +		base += copy; +		if (iov->iov_len == base) { +			iov++; +			nr_segs--; +			base = 0; +		}  	} +	memcpy(new, iov, sizeof(*iov) * nr_segs); +	new->iov_base += base; +	new->iov_len -= base; +	return nr_segs; +} -	/* find end of first SMB data area */ -	data_area_of_target = (char *)&pSMBt->hdr.Protocol + -				le16_to_cpu(pSMBt->t2_rsp.DataOffset); -	/* validate target area */ - -	data_area_of_buf2 = (char *) &pSMB2->hdr.Protocol + -					le16_to_cpu(pSMB2->t2_rsp.DataOffset); - -	data_area_of_target += total_in_buf; - -	/* copy second buffer into end of first buffer */ -	memcpy(data_area_of_target, data_area_of_buf2, total_in_buf2); -	total_in_buf += total_in_buf2; -	pSMBt->t2_rsp.DataCount = cpu_to_le16(total_in_buf); -	byte_count = le16_to_cpu(BCC_LE(pTargetSMB)); -	byte_count += total_in_buf2; -	BCC_LE(pTargetSMB) = cpu_to_le16(byte_count); - -	byte_count = pTargetSMB->smb_buf_length; -	byte_count += total_in_buf2; - -	/* BB also add check that we are not beyond maximum buffer size */ - -	pTargetSMB->smb_buf_length = byte_count; +static struct kvec * +get_server_iovec(struct TCP_Server_Info *server, unsigned int nr_segs) +{ +	struct kvec *new_iov; -	if (remaining == total_in_buf2) { -		cFYI(1, "found the last secondary response"); -		return 0; /* we are done */ -	} else /* more responses to go */ -		return 1; +	if (server->iov && nr_segs <= server->nr_iov) +		return server->iov; +	/* not big enough -- allocate a new one and release the old */ +	new_iov = kmalloc(sizeof(*new_iov) * nr_segs, GFP_NOFS); +	if (new_iov) { +		kfree(server->iov); +		server->iov = new_iov; +		server->nr_iov = nr_segs; +	} +	return new_iov;  } -static int -cifs_demultiplex_thread(struct TCP_Server_Info *server) +int +cifs_readv_from_socket(struct TCP_Server_Info *server, struct kvec *iov_orig, +		       unsigned int nr_segs, unsigned int to_read)  { -	int length; -	unsigned int pdu_length, total_read; -	struct smb_hdr *smb_buffer = NULL; -	struct smb_hdr *bigbuf = NULL; -	struct smb_hdr *smallbuf = NULL; +	int length = 0; +	int total_read; +	unsigned int segs;  	struct msghdr smb_msg; -	struct kvec iov; -	struct socket *csocket = server->ssocket; -	struct list_head *tmp; -	struct cifsSesInfo *ses; -	struct task_struct *task_to_wake = NULL; -	struct mid_q_entry *mid_entry; -	char temp; -	bool isLargeBuf = false; -	bool isMultiRsp; -	int reconnect; +	struct kvec *iov; -	current->flags |= PF_MEMALLOC; -	cFYI(1, "Demultiplex PID: %d", task_pid_nr(current)); +	iov = get_server_iovec(server, nr_segs); +	if (!iov) +		return -ENOMEM; -	length = atomic_inc_return(&tcpSesAllocCount); -	if (length > 1) -		mempool_resize(cifs_req_poolp, length + cifs_min_rcv, -				GFP_KERNEL); +	smb_msg.msg_control = NULL; +	smb_msg.msg_controllen = 0; -	set_freezable(); -	while (server->tcpStatus != CifsExiting) { -		if (try_to_freeze()) -			continue; -		if (bigbuf == NULL) { -			bigbuf = cifs_buf_get(); -			if (!bigbuf) { -				cERROR(1, "No memory for large SMB response"); -				msleep(3000); -				/* retry will check if exiting */ -				continue; -			} -		} else if (isLargeBuf) { -			/* we are reusing a dirty large buf, clear its start */ -			memset(bigbuf, 0, sizeof(struct smb_hdr)); +	for (total_read = 0; to_read; total_read += length, to_read -= length) { +		try_to_freeze(); + +		if (server_unresponsive(server)) { +			total_read = -EAGAIN; +			break;  		} -		if (smallbuf == NULL) { -			smallbuf = cifs_small_buf_get(); -			if (!smallbuf) { -				cERROR(1, "No memory for SMB response"); -				msleep(1000); -				/* retry will check if exiting */ -				continue; -			} -			/* beginning of smb buffer is cleared in our buf_get */ -		} else /* if existing small buf clear beginning */ -			memset(smallbuf, 0, sizeof(struct smb_hdr)); - -		isLargeBuf = false; -		isMultiRsp = false; -		smb_buffer = smallbuf; -		iov.iov_base = smb_buffer; -		iov.iov_len = 4; -		smb_msg.msg_control = NULL; -		smb_msg.msg_controllen = 0; -		pdu_length = 4; /* enough to get RFC1001 header */ -incomplete_rcv: -		length = -		    kernel_recvmsg(csocket, &smb_msg, -				&iov, 1, pdu_length, 0 /* BB other flags? */); +		segs = kvec_array_init(iov, iov_orig, nr_segs, total_read); + +		length = kernel_recvmsg(server->ssocket, &smb_msg, +					iov, segs, to_read, 0);  		if (server->tcpStatus == CifsExiting) { +			total_read = -ESHUTDOWN;  			break;  		} else if (server->tcpStatus == CifsNeedReconnect) { -			cFYI(1, "Reconnect after server stopped responding");  			cifs_reconnect(server); -			cFYI(1, "call to reconnect done"); -			csocket = server->ssocket; -			continue; +			total_read = -EAGAIN; +			break;  		} else if (length == -ERESTARTSYS ||  			   length == -EAGAIN ||  			   length == -EINTR) { -			msleep(1); /* minimum sleep to prevent looping -				allowing socket to clear and app threads to set -				tcpStatus CifsNeedReconnect if server hung */ -			if (pdu_length < 4) { -				iov.iov_base = (4 - pdu_length) + -							(char *)smb_buffer; -				iov.iov_len = pdu_length; -				smb_msg.msg_control = NULL; -				smb_msg.msg_controllen = 0; -				goto incomplete_rcv; -			} else -				continue; -		} else if (length <= 0) { -			cFYI(1, "Reconnect after unexpected peek error %d", -				length); -			cifs_reconnect(server); -			csocket = server->ssocket; -			wake_up(&server->response_q); -			continue; -		} else if (length < pdu_length) { -			cFYI(1, "requested %d bytes but only got %d bytes", -				  pdu_length, length); -			pdu_length -= length; -			msleep(1); -			goto incomplete_rcv; -		} - -		/* The right amount was read from socket - 4 bytes */ -		/* so we can now interpret the length field */ - -		/* the first byte big endian of the length field, -		is actually not part of the length but the type -		with the most common, zero, as regular data */ -		temp = *((char *) smb_buffer); - -		/* Note that FC 1001 length is big endian on the wire, -		but we convert it here so it is always manipulated -		as host byte order */ -		pdu_length = be32_to_cpu((__force __be32)smb_buffer->smb_buf_length); -		smb_buffer->smb_buf_length = pdu_length; - -		cFYI(1, "rfc1002 length 0x%x", pdu_length+4); - -		if (temp == (char) RFC1002_SESSION_KEEP_ALIVE) { -			continue; -		} else if (temp == (char)RFC1002_POSITIVE_SESSION_RESPONSE) { -			cFYI(1, "Good RFC 1002 session rsp"); -			continue; -		} else if (temp == (char)RFC1002_NEGATIVE_SESSION_RESPONSE) { -			/* we get this from Windows 98 instead of -			   an error on SMB negprot response */ -			cFYI(1, "Negative RFC1002 Session Response Error 0x%x)", -				pdu_length); -			/* give server a second to clean up  */ -			msleep(1000); -			/* always try 445 first on reconnect since we get NACK -			 * on some if we ever connected to port 139 (the NACK -			 * is since we do not begin with RFC1001 session -			 * initialize frame) +			/* +			 * Minimum sleep to prevent looping, allowing socket +			 * to clear and app threads to set tcpStatus +			 * CifsNeedReconnect if server hung.  			 */ -			cifs_set_port((struct sockaddr *) -					&server->addr.sockAddr, CIFS_PORT); -			cifs_reconnect(server); -			csocket = server->ssocket; -			wake_up(&server->response_q); -			continue; -		} else if (temp != (char) 0) { -			cERROR(1, "Unknown RFC 1002 frame"); -			cifs_dump_mem(" Received Data: ", (char *)smb_buffer, -				      length); -			cifs_reconnect(server); -			csocket = server->ssocket; +			usleep_range(1000, 2000); +			length = 0;  			continue; -		} - -		/* else we have an SMB response */ -		if ((pdu_length > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4) || -			    (pdu_length < sizeof(struct smb_hdr) - 1 - 4)) { -			cERROR(1, "Invalid size SMB length %d pdu_length %d", -					length, pdu_length+4); +		} else if (length <= 0) { +			cifs_dbg(FYI, "Received no data or error: expecting %d\n" +				 "got %d", to_read, length);  			cifs_reconnect(server); -			csocket = server->ssocket; -			wake_up(&server->response_q); -			continue; -		} - -		/* else length ok */ -		reconnect = 0; - -		if (pdu_length > MAX_CIFS_SMALL_BUFFER_SIZE - 4) { -			isLargeBuf = true; -			memcpy(bigbuf, smallbuf, 4); -			smb_buffer = bigbuf; -		} -		length = 0; -		iov.iov_base = 4 + (char *)smb_buffer; -		iov.iov_len = pdu_length; -		for (total_read = 0; total_read < pdu_length; -		     total_read += length) { -			length = kernel_recvmsg(csocket, &smb_msg, &iov, 1, -						pdu_length - total_read, 0); -			if (server->tcpStatus == CifsExiting) { -				/* then will exit */ -				reconnect = 2; -				break; -			} else if (server->tcpStatus == CifsNeedReconnect) { -				cifs_reconnect(server); -				csocket = server->ssocket; -				/* Reconnect wakes up rspns q */ -				/* Now we will reread sock */ -				reconnect = 1; -				break; -			} else if (length == -ERESTARTSYS || -				   length == -EAGAIN || -				   length == -EINTR) { -				msleep(1); /* minimum sleep to prevent looping, -					      allowing socket to clear and app -					      threads to set tcpStatus -					      CifsNeedReconnect if server hung*/ -				length = 0; -				continue; -			} else if (length <= 0) { -				cERROR(1, "Received no data, expecting %d", -					      pdu_length - total_read); -				cifs_reconnect(server); -				csocket = server->ssocket; -				reconnect = 1; -				break; -			} -		} -		if (reconnect == 2) +			total_read = -EAGAIN;  			break; -		else if (reconnect == 1) -			continue; +		} +	} +	return total_read; +} -		length += 4; /* account for rfc1002 hdr */ +int +cifs_read_from_socket(struct TCP_Server_Info *server, char *buf, +		      unsigned int to_read) +{ +	struct kvec iov; +	iov.iov_base = buf; +	iov.iov_len = to_read; -		dump_smb(smb_buffer, length); -		if (checkSMB(smb_buffer, smb_buffer->Mid, total_read+4)) { -			cifs_dump_mem("Bad SMB: ", smb_buffer, 48); -			continue; -		} +	return cifs_readv_from_socket(server, &iov, 1, to_read); +} +static bool +is_smb_response(struct TCP_Server_Info *server, unsigned char type) +{ +	/* +	 * The first byte big endian of the length field, +	 * is actually not part of the length but the type +	 * with the most common, zero, as regular data. +	 */ +	switch (type) { +	case RFC1002_SESSION_MESSAGE: +		/* Regular SMB response */ +		return true; +	case RFC1002_SESSION_KEEP_ALIVE: +		cifs_dbg(FYI, "RFC 1002 session keep alive\n"); +		break; +	case RFC1002_POSITIVE_SESSION_RESPONSE: +		cifs_dbg(FYI, "RFC 1002 positive session response\n"); +		break; +	case RFC1002_NEGATIVE_SESSION_RESPONSE: +		/* +		 * We get this from Windows 98 instead of an error on +		 * SMB negprot response. +		 */ +		cifs_dbg(FYI, "RFC 1002 negative session response\n"); +		/* give server a second to clean up */ +		msleep(1000); +		/* +		 * Always try 445 first on reconnect since we get NACK +		 * on some if we ever connected to port 139 (the NACK +		 * is since we do not begin with RFC1001 session +		 * initialize frame). +		 */ +		cifs_set_port((struct sockaddr *)&server->dstaddr, CIFS_PORT); +		cifs_reconnect(server); +		wake_up(&server->response_q); +		break; +	default: +		cifs_dbg(VFS, "RFC 1002 unknown response type 0x%x\n", type); +		cifs_reconnect(server); +	} -		task_to_wake = NULL; -		spin_lock(&GlobalMid_Lock); -		list_for_each(tmp, &server->pending_mid_q) { -			mid_entry = list_entry(tmp, struct mid_q_entry, qhead); +	return false; +} -			if ((mid_entry->mid == smb_buffer->Mid) && -			    (mid_entry->midState == MID_REQUEST_SUBMITTED) && -			    (mid_entry->command == smb_buffer->Command)) { -				if (check2ndT2(smb_buffer,server->maxBuf) > 0) { -					/* We have a multipart transact2 resp */ -					isMultiRsp = true; -					if (mid_entry->resp_buf) { -						/* merge response - fix up 1st*/ -						if (coalesce_t2(smb_buffer, -							mid_entry->resp_buf)) { -							mid_entry->multiRsp = -								 true; -							break; -						} else { -							/* all parts received */ -							mid_entry->multiEnd = -								 true; -							goto multi_t2_fnd; -						} -					} else { -						if (!isLargeBuf) { -							cERROR(1, "1st trans2 resp needs bigbuf"); -					/* BB maybe we can fix this up,  switch -					   to already allocated large buffer? */ -						} else { -							/* Have first buffer */ -							mid_entry->resp_buf = -								 smb_buffer; -							mid_entry->largeBuf = -								 true; -							bigbuf = NULL; -						} -					} -					break; -				} -				mid_entry->resp_buf = smb_buffer; -				mid_entry->largeBuf = isLargeBuf; -multi_t2_fnd: -				task_to_wake = mid_entry->tsk; -				mid_entry->midState = MID_RESPONSE_RECEIVED; +void +dequeue_mid(struct mid_q_entry *mid, bool malformed) +{  #ifdef CONFIG_CIFS_STATS2 -				mid_entry->when_received = jiffies; +	mid->when_received = jiffies;  #endif -				/* so we do not time out requests to  server -				which is still responding (since server could -				be busy but not dead) */ -				server->lstrp = jiffies; -				break; -			} -		} -		spin_unlock(&GlobalMid_Lock); -		if (task_to_wake) { -			/* Was previous buf put in mpx struct for multi-rsp? */ -			if (!isMultiRsp) { -				/* smb buffer will be freed by user thread */ -				if (isLargeBuf) -					bigbuf = NULL; -				else -					smallbuf = NULL; -			} -			wake_up_process(task_to_wake); -		} else if (!is_valid_oplock_break(smb_buffer, server) && -			   !isMultiRsp) { -			cERROR(1, "No task to wake, unknown frame received! " -				   "NumMids %d", midCount.counter); -			cifs_dump_mem("Received Data is: ", (char *)smb_buffer, -				      sizeof(struct smb_hdr)); -#ifdef CONFIG_CIFS_DEBUG2 -			cifs_dump_detail(smb_buffer); -			cifs_dump_mids(server); -#endif /* CIFS_DEBUG2 */ +	spin_lock(&GlobalMid_Lock); +	if (!malformed) +		mid->mid_state = MID_RESPONSE_RECEIVED; +	else +		mid->mid_state = MID_RESPONSE_MALFORMED; +	list_del_init(&mid->qhead); +	spin_unlock(&GlobalMid_Lock); +} -		} -	} /* end while !EXITING */ +static void +handle_mid(struct mid_q_entry *mid, struct TCP_Server_Info *server, +	   char *buf, int malformed) +{ +	if (server->ops->check_trans2 && +	    server->ops->check_trans2(mid, server, buf, malformed)) +		return; +	mid->resp_buf = buf; +	mid->large_buf = server->large_buf; +	/* Was previous buf put in mpx struct for multi-rsp? */ +	if (!mid->multiRsp) { +		/* smb buffer will be freed by user thread */ +		if (server->large_buf) +			server->bigbuf = NULL; +		else +			server->smallbuf = NULL; +	} +	dequeue_mid(mid, malformed); +} + +static void clean_demultiplex_info(struct TCP_Server_Info *server) +{ +	int length;  	/* take it off the list, if it's not already */  	spin_lock(&cifs_tcp_ses_lock); @@ -651,107 +701,235 @@ multi_t2_fnd:  	wake_up_all(&server->response_q);  	/* check if we have blocked requests that need to free */ -	/* Note that cifs_max_pending is normally 50, but -	can be set at module install time to as little as two */ -	spin_lock(&GlobalMid_Lock); -	if (atomic_read(&server->inFlight) >= cifs_max_pending) -		atomic_set(&server->inFlight, cifs_max_pending - 1); -	/* We do not want to set the max_pending too low or we -	could end up with the counter going negative */ -	spin_unlock(&GlobalMid_Lock); -	/* Although there should not be any requests blocked on -	this queue it can not hurt to be paranoid and try to wake up requests -	that may haven been blocked when more than 50 at time were on the wire -	to the same server - they now will see the session is in exit state -	and get out of SendReceive.  */ +	spin_lock(&server->req_lock); +	if (server->credits <= 0) +		server->credits = 1; +	spin_unlock(&server->req_lock); +	/* +	 * Although there should not be any requests blocked on this queue it +	 * can not hurt to be paranoid and try to wake up requests that may +	 * haven been blocked when more than 50 at time were on the wire to the +	 * same server - they now will see the session is in exit state and get +	 * out of SendReceive. +	 */  	wake_up_all(&server->request_q);  	/* give those requests time to exit */  	msleep(125);  	if (server->ssocket) { -		sock_release(csocket); +		sock_release(server->ssocket);  		server->ssocket = NULL;  	} -	/* buffer usuallly freed in free_mid - need to free it here on exit */ -	cifs_buf_release(bigbuf); -	if (smallbuf) /* no sense logging a debug message if NULL */ -		cifs_small_buf_release(smallbuf); -	/* -	 * BB: we shouldn't have to do any of this. It shouldn't be -	 * possible to exit from the thread with active SMB sessions -	 */ -	spin_lock(&cifs_tcp_ses_lock); -	if (list_empty(&server->pending_mid_q)) { -		/* loop through server session structures attached to this and -		    mark them dead */ -		list_for_each(tmp, &server->smb_ses_list) { -			ses = list_entry(tmp, struct cifsSesInfo, -					 smb_ses_list); -			ses->status = CifsExiting; -			ses->server = NULL; -		} -		spin_unlock(&cifs_tcp_ses_lock); -	} else { -		/* although we can not zero the server struct pointer yet, -		since there are active requests which may depnd on them, -		mark the corresponding SMB sessions as exiting too */ -		list_for_each(tmp, &server->smb_ses_list) { -			ses = list_entry(tmp, struct cifsSesInfo, -					 smb_ses_list); -			ses->status = CifsExiting; -		} +	if (!list_empty(&server->pending_mid_q)) { +		struct list_head dispose_list; +		struct mid_q_entry *mid_entry; +		struct list_head *tmp, *tmp2; +		INIT_LIST_HEAD(&dispose_list);  		spin_lock(&GlobalMid_Lock); -		list_for_each(tmp, &server->pending_mid_q) { -		mid_entry = list_entry(tmp, struct mid_q_entry, qhead); -			if (mid_entry->midState == MID_REQUEST_SUBMITTED) { -				cFYI(1, "Clearing Mid 0x%x - waking up ", -					 mid_entry->mid); -				task_to_wake = mid_entry->tsk; -				if (task_to_wake) -					wake_up_process(task_to_wake); -			} +		list_for_each_safe(tmp, tmp2, &server->pending_mid_q) { +			mid_entry = list_entry(tmp, struct mid_q_entry, qhead); +			cifs_dbg(FYI, "Clearing mid 0x%llx\n", mid_entry->mid); +			mid_entry->mid_state = MID_SHUTDOWN; +			list_move(&mid_entry->qhead, &dispose_list);  		}  		spin_unlock(&GlobalMid_Lock); -		spin_unlock(&cifs_tcp_ses_lock); + +		/* now walk dispose list and issue callbacks */ +		list_for_each_safe(tmp, tmp2, &dispose_list) { +			mid_entry = list_entry(tmp, struct mid_q_entry, qhead); +			cifs_dbg(FYI, "Callback mid 0x%llx\n", mid_entry->mid); +			list_del_init(&mid_entry->qhead); +			mid_entry->callback(mid_entry); +		}  		/* 1/8th of sec is more than enough time for them to exit */  		msleep(125);  	}  	if (!list_empty(&server->pending_mid_q)) { -		/* mpx threads have not exited yet give them -		at least the smb send timeout time for long ops */ -		/* due to delays on oplock break requests, we need -		to wait at least 45 seconds before giving up -		on a request getting a response and going ahead -		and killing cifsd */ -		cFYI(1, "Wait for exit from demultiplex thread"); +		/* +		 * mpx threads have not exited yet give them at least the smb +		 * send timeout time for long ops. +		 * +		 * Due to delays on oplock break requests, we need to wait at +		 * least 45 seconds before giving up on a request getting a +		 * response and going ahead and killing cifsd. +		 */ +		cifs_dbg(FYI, "Wait for exit from demultiplex thread\n");  		msleep(46000); -		/* if threads still have not exited they are probably never -		coming home not much else we can do but free the memory */ -	} - -	/* last chance to mark ses pointers invalid -	if there are any pointing to this (e.g -	if a crazy root user tried to kill cifsd -	kernel thread explicitly this might happen) */ -	/* BB: This shouldn't be necessary, see above */ -	spin_lock(&cifs_tcp_ses_lock); -	list_for_each(tmp, &server->smb_ses_list) { -		ses = list_entry(tmp, struct cifsSesInfo, smb_ses_list); -		ses->server = NULL; +		/* +		 * If threads still have not exited they are probably never +		 * coming home not much else we can do but free the memory. +		 */  	} -	spin_unlock(&cifs_tcp_ses_lock);  	kfree(server->hostname); -	task_to_wake = xchg(&server->tsk, NULL); +	kfree(server->iov);  	kfree(server);  	length = atomic_dec_return(&tcpSesAllocCount); -	if (length  > 0) +	if (length > 0)  		mempool_resize(cifs_req_poolp, length + cifs_min_rcv,  				GFP_KERNEL); +} + +static int +standard_receive3(struct TCP_Server_Info *server, struct mid_q_entry *mid) +{ +	int length; +	char *buf = server->smallbuf; +	unsigned int pdu_length = get_rfc1002_length(buf); + +	/* make sure this will fit in a large buffer */ +	if (pdu_length > CIFSMaxBufSize + MAX_HEADER_SIZE(server) - 4) { +		cifs_dbg(VFS, "SMB response too long (%u bytes)\n", pdu_length); +		cifs_reconnect(server); +		wake_up(&server->response_q); +		return -EAGAIN; +	} + +	/* switch to large buffer if too big for a small one */ +	if (pdu_length > MAX_CIFS_SMALL_BUFFER_SIZE - 4) { +		server->large_buf = true; +		memcpy(server->bigbuf, buf, server->total_read); +		buf = server->bigbuf; +	} + +	/* now read the rest */ +	length = cifs_read_from_socket(server, buf + HEADER_SIZE(server) - 1, +				pdu_length - HEADER_SIZE(server) + 1 + 4); +	if (length < 0) +		return length; +	server->total_read += length; + +	dump_smb(buf, server->total_read); + +	/* +	 * We know that we received enough to get to the MID as we +	 * checked the pdu_length earlier. Now check to see +	 * if the rest of the header is OK. We borrow the length +	 * var for the rest of the loop to avoid a new stack var. +	 * +	 * 48 bytes is enough to display the header and a little bit +	 * into the payload for debugging purposes. +	 */ +	length = server->ops->check_message(buf, server->total_read); +	if (length != 0) +		cifs_dump_mem("Bad SMB: ", buf, +			min_t(unsigned int, server->total_read, 48)); + +	if (server->ops->is_status_pending && +	    server->ops->is_status_pending(buf, server, length)) +		return -1; + +	if (!mid) +		return length; + +	handle_mid(mid, server, buf, length); +	return 0; +} + +static int +cifs_demultiplex_thread(void *p) +{ +	int length; +	struct TCP_Server_Info *server = p; +	unsigned int pdu_length; +	char *buf = NULL; +	struct task_struct *task_to_wake = NULL; +	struct mid_q_entry *mid_entry; + +	current->flags |= PF_MEMALLOC; +	cifs_dbg(FYI, "Demultiplex PID: %d\n", task_pid_nr(current)); + +	length = atomic_inc_return(&tcpSesAllocCount); +	if (length > 1) +		mempool_resize(cifs_req_poolp, length + cifs_min_rcv, +				GFP_KERNEL); + +	set_freezable(); +	while (server->tcpStatus != CifsExiting) { +		if (try_to_freeze()) +			continue; + +		if (!allocate_buffers(server)) +			continue; + +		server->large_buf = false; +		buf = server->smallbuf; +		pdu_length = 4; /* enough to get RFC1001 header */ + +		length = cifs_read_from_socket(server, buf, pdu_length); +		if (length < 0) +			continue; +		server->total_read = length; + +		/* +		 * The right amount was read from socket - 4 bytes, +		 * so we can now interpret the length field. +		 */ +		pdu_length = get_rfc1002_length(buf); + +		cifs_dbg(FYI, "RFC1002 header 0x%x\n", pdu_length); +		if (!is_smb_response(server, buf[0])) +			continue; + +		/* make sure we have enough to get to the MID */ +		if (pdu_length < HEADER_SIZE(server) - 1 - 4) { +			cifs_dbg(VFS, "SMB response too short (%u bytes)\n", +				 pdu_length); +			cifs_reconnect(server); +			wake_up(&server->response_q); +			continue; +		} + +		/* read down to the MID */ +		length = cifs_read_from_socket(server, buf + 4, +					       HEADER_SIZE(server) - 1 - 4); +		if (length < 0) +			continue; +		server->total_read += length; + +		mid_entry = server->ops->find_mid(server, buf); + +		if (!mid_entry || !mid_entry->receive) +			length = standard_receive3(server, mid_entry); +		else +			length = mid_entry->receive(server, mid_entry); + +		if (length < 0) +			continue; + +		if (server->large_buf) +			buf = server->bigbuf; + +		server->lstrp = jiffies; +		if (mid_entry != NULL) { +			if (!mid_entry->multiRsp || mid_entry->multiEnd) +				mid_entry->callback(mid_entry); +		} else if (!server->ops->is_oplock_break || +			   !server->ops->is_oplock_break(buf, server)) { +			cifs_dbg(VFS, "No task to wake, unknown frame received! NumMids %d\n", +				 atomic_read(&midCount)); +			cifs_dump_mem("Received Data is: ", buf, +				      HEADER_SIZE(server)); +#ifdef CONFIG_CIFS_DEBUG2 +			if (server->ops->dump_detail) +				server->ops->dump_detail(buf); +			cifs_dump_mids(server); +#endif /* CIFS_DEBUG2 */ + +		} +	} /* end while !EXITING */ + +	/* buffer usually freed in free_mid - need to free it here on exit */ +	cifs_buf_release(server->bigbuf); +	if (server->smallbuf) /* no sense logging a debug message if NULL */ +		cifs_small_buf_release(server->smallbuf); + +	task_to_wake = xchg(&server->tsk, NULL); +	clean_demultiplex_info(server);  	/* if server->tsk was NULL then wait for a signal before exiting */  	if (!task_to_wake) { @@ -794,36 +972,258 @@ extract_hostname(const char *unc)  	return dst;  } +static int get_option_ul(substring_t args[], unsigned long *option) +{ +	int rc; +	char *string; + +	string = match_strdup(args); +	if (string == NULL) +		return -ENOMEM; +	rc = kstrtoul(string, 0, option); +	kfree(string); + +	return rc; +} + +static int get_option_uid(substring_t args[], kuid_t *result) +{ +	unsigned long value; +	kuid_t uid; +	int rc; + +	rc = get_option_ul(args, &value); +	if (rc) +		return rc; + +	uid = make_kuid(current_user_ns(), value); +	if (!uid_valid(uid)) +		return -EINVAL; + +	*result = uid; +	return 0; +} + +static int get_option_gid(substring_t args[], kgid_t *result) +{ +	unsigned long value; +	kgid_t gid; +	int rc; + +	rc = get_option_ul(args, &value); +	if (rc) +		return rc; + +	gid = make_kgid(current_user_ns(), value); +	if (!gid_valid(gid)) +		return -EINVAL; + +	*result = gid; +	return 0; +} + +static int cifs_parse_security_flavors(char *value, +				       struct smb_vol *vol) +{ + +	substring_t args[MAX_OPT_ARGS]; + +	/* +	 * With mount options, the last one should win. Reset any existing +	 * settings back to default. +	 */ +	vol->sectype = Unspecified; +	vol->sign = false; + +	switch (match_token(value, cifs_secflavor_tokens, args)) { +	case Opt_sec_krb5p: +		cifs_dbg(VFS, "sec=krb5p is not supported!\n"); +		return 1; +	case Opt_sec_krb5i: +		vol->sign = true; +		/* Fallthrough */ +	case Opt_sec_krb5: +		vol->sectype = Kerberos; +		break; +	case Opt_sec_ntlmsspi: +		vol->sign = true; +		/* Fallthrough */ +	case Opt_sec_ntlmssp: +		vol->sectype = RawNTLMSSP; +		break; +	case Opt_sec_ntlmi: +		vol->sign = true; +		/* Fallthrough */ +	case Opt_ntlm: +		vol->sectype = NTLM; +		break; +	case Opt_sec_ntlmv2i: +		vol->sign = true; +		/* Fallthrough */ +	case Opt_sec_ntlmv2: +		vol->sectype = NTLMv2; +		break; +#ifdef CONFIG_CIFS_WEAK_PW_HASH +	case Opt_sec_lanman: +		vol->sectype = LANMAN; +		break; +#endif +	case Opt_sec_none: +		vol->nullauth = 1; +		break; +	default: +		cifs_dbg(VFS, "bad security option: %s\n", value); +		return 1; +	} + +	return 0; +} + +static int +cifs_parse_cache_flavor(char *value, struct smb_vol *vol) +{ +	substring_t args[MAX_OPT_ARGS]; + +	switch (match_token(value, cifs_cacheflavor_tokens, args)) { +	case Opt_cache_loose: +		vol->direct_io = false; +		vol->strict_io = false; +		break; +	case Opt_cache_strict: +		vol->direct_io = false; +		vol->strict_io = true; +		break; +	case Opt_cache_none: +		vol->direct_io = true; +		vol->strict_io = false; +		break; +	default: +		cifs_dbg(VFS, "bad cache= option: %s\n", value); +		return 1; +	} +	return 0; +} + +static int +cifs_parse_smb_version(char *value, struct smb_vol *vol) +{ +	substring_t args[MAX_OPT_ARGS]; + +	switch (match_token(value, cifs_smb_version_tokens, args)) { +	case Smb_1: +		vol->ops = &smb1_operations; +		vol->vals = &smb1_values; +		break; +#ifdef CONFIG_CIFS_SMB2 +	case Smb_20: +		vol->ops = &smb20_operations; +		vol->vals = &smb20_values; +		break; +	case Smb_21: +		vol->ops = &smb21_operations; +		vol->vals = &smb21_values; +		break; +	case Smb_30: +		vol->ops = &smb30_operations; +		vol->vals = &smb30_values; +		break; +	case Smb_302: +		vol->ops = &smb30_operations; /* currently identical with 3.0 */ +		vol->vals = &smb302_values; +		break; +#endif +	default: +		cifs_dbg(VFS, "Unknown vers= option specified: %s\n", value); +		return 1; +	} +	return 0; +} + +/* + * Parse a devname into substrings and populate the vol->UNC and vol->prepath + * fields with the result. Returns 0 on success and an error otherwise. + */  static int -cifs_parse_mount_options(char *options, const char *devname, +cifs_parse_devname(const char *devname, struct smb_vol *vol) +{ +	char *pos; +	const char *delims = "/\\"; +	size_t len; + +	/* make sure we have a valid UNC double delimiter prefix */ +	len = strspn(devname, delims); +	if (len != 2) +		return -EINVAL; + +	/* find delimiter between host and sharename */ +	pos = strpbrk(devname + 2, delims); +	if (!pos) +		return -EINVAL; + +	/* skip past delimiter */ +	++pos; + +	/* now go until next delimiter or end of string */ +	len = strcspn(pos, delims); + +	/* move "pos" up to delimiter or NULL */ +	pos += len; +	vol->UNC = kstrndup(devname, pos - devname, GFP_KERNEL); +	if (!vol->UNC) +		return -ENOMEM; + +	convert_delimiter(vol->UNC, '\\'); + +	/* If pos is NULL, or is a bogus trailing delimiter then no prepath */ +	if (!*pos++ || !*pos) +		return 0; + +	vol->prepath = kstrdup(pos, GFP_KERNEL); +	if (!vol->prepath) +		return -ENOMEM; + +	return 0; +} + +static int +cifs_parse_mount_options(const char *mountdata, const char *devname,  			 struct smb_vol *vol)  { -	char *value; -	char *data; +	char *data, *end; +	char *mountdata_copy = NULL, *options;  	unsigned int  temp_len, i, j;  	char separator[2];  	short int override_uid = -1;  	short int override_gid = -1;  	bool uid_specified = false;  	bool gid_specified = false; +	bool sloppy = false; +	char *invalid = NULL; +	char *nodename = utsname()->nodename; +	char *string = NULL; +	char *tmp_end, *value; +	char delim; +	bool got_ip = false; +	unsigned short port = 0; +	struct sockaddr *dstaddr = (struct sockaddr *)&vol->dstaddr;  	separator[0] = ',';  	separator[1] = 0; +	delim = separator[0]; -	if (Local_System_Name[0] != 0) -		memcpy(vol->source_rfc1001_name, Local_System_Name, 15); -	else { -		char *nodename = utsname()->nodename; -		int n = strnlen(nodename, 15); -		memset(vol->source_rfc1001_name, 0x20, 15); -		for (i = 0; i < n; i++) { -			/* does not have to be perfect mapping since field is -			informational, only used for servers that do not support -			port 445 and it can be overridden at mount time */ -			vol->source_rfc1001_name[i] = toupper(nodename[i]); -		} -	} -	vol->source_rfc1001_name[15] = 0; +	/* ensure we always start with zeroed-out smb_vol */ +	memset(vol, 0, sizeof(*vol)); + +	/* +	 * does not have to be perfect mapping since field is +	 * informational, only used for servers that do not support +	 * port 445 and it can be overridden at mount time +	 */ +	memset(vol->source_rfc1001_name, 0x20, RFC1001_NAME_LEN); +	for (i = 0; i < strnlen(nodename, RFC1001_NAME_LEN); i++) +		vol->source_rfc1001_name[i] = toupper(nodename[i]); + +	vol->source_rfc1001_name[RFC1001_NAME_LEN] = 0;  	/* null target name indicates to use *SMBSERVR default called name  	   if we end up sending RFC1001 session initialize */  	vol->target_rfc1001_name[0] = 0; @@ -840,563 +1240,635 @@ cifs_parse_mount_options(char *options, const char *devname,  	/* default to using server inode numbers where available */  	vol->server_ino = 1; -	if (!options) -		return 1; +	/* default is to use strict cifs caching semantics */ +	vol->strict_io = true; + +	vol->actimeo = CIFS_DEF_ACTIMEO; + +	/* FIXME: add autonegotiation -- for now, SMB1 is default */ +	vol->ops = &smb1_operations; +	vol->vals = &smb1_values; + +	if (!mountdata) +		goto cifs_parse_mount_err; + +	mountdata_copy = kstrndup(mountdata, PAGE_SIZE, GFP_KERNEL); +	if (!mountdata_copy) +		goto cifs_parse_mount_err; + +	options = mountdata_copy; +	end = options + strlen(options);  	if (strncmp(options, "sep=", 4) == 0) {  		if (options[4] != 0) {  			separator[0] = options[4];  			options += 5;  		} else { -			cFYI(1, "Null separator not allowed"); +			cifs_dbg(FYI, "Null separator not allowed\n");  		}  	} +	vol->backupuid_specified = false; /* no backup intent for a user */ +	vol->backupgid_specified = false; /* no backup intent for a group */ + +	switch (cifs_parse_devname(devname, vol)) { +	case 0: +		break; +	case -ENOMEM: +		cifs_dbg(VFS, "Unable to allocate memory for devname.\n"); +		goto cifs_parse_mount_err; +	case -EINVAL: +		cifs_dbg(VFS, "Malformed UNC in devname.\n"); +		goto cifs_parse_mount_err; +	default: +		cifs_dbg(VFS, "Unknown error parsing devname.\n"); +		goto cifs_parse_mount_err; +	}  	while ((data = strsep(&options, separator)) != NULL) { +		substring_t args[MAX_OPT_ARGS]; +		unsigned long option; +		int token; +  		if (!*data)  			continue; -		if ((value = strchr(data, '=')) != NULL) -			*value++ = '\0'; -		/* Have to parse this before we parse for "user" */ -		if (strnicmp(data, "user_xattr", 10) == 0) { +		token = match_token(data, cifs_mount_option_tokens, args); + +		switch (token) { + +		/* Ingnore the following */ +		case Opt_ignore: +			break; + +		/* Boolean values */ +		case Opt_user_xattr:  			vol->no_xattr = 0; -		} else if (strnicmp(data, "nouser_xattr", 12) == 0) { +			break; +		case Opt_nouser_xattr:  			vol->no_xattr = 1; -		} else if (strnicmp(data, "user", 4) == 0) { -			if (!value) { -				printk(KERN_WARNING -				       "CIFS: invalid or missing username\n"); -				return 1;	/* needs_arg; */ -			} else if (!*value) { -				/* null user, ie anonymous, authentication */ -				vol->nullauth = 1; -			} -			if (strnlen(value, 200) < 200) { -				vol->username = value; -			} else { -				printk(KERN_WARNING "CIFS: username too long\n"); -				return 1; -			} -		} else if (strnicmp(data, "pass", 4) == 0) { -			if (!value) { -				vol->password = NULL; -				continue; -			} else if (value[0] == 0) { -				/* check if string begins with double comma -				   since that would mean the password really -				   does start with a comma, and would not -				   indicate an empty string */ -				if (value[1] != separator[0]) { -					vol->password = NULL; -					continue; -				} -			} -			temp_len = strlen(value); -			/* removed password length check, NTLM passwords -				can be arbitrarily long */ - -			/* if comma in password, the string will be -			prematurely null terminated.  Commas in password are -			specified across the cifs mount interface by a double -			comma ie ,, and a comma used as in other cases ie ',' -			as a parameter delimiter/separator is single and due -			to the strsep above is temporarily zeroed. */ - -			/* NB: password legally can have multiple commas and -			the only illegal character in a password is null */ - -			if ((value[temp_len] == 0) && -			    (value[temp_len+1] == separator[0])) { -				/* reinsert comma */ -				value[temp_len] = separator[0]; -				temp_len += 2;  /* move after second comma */ -				while (value[temp_len] != 0)  { -					if (value[temp_len] == separator[0]) { -						if (value[temp_len+1] == -						     separator[0]) { -						/* skip second comma */ -							temp_len++; -						} else { -						/* single comma indicating start -							 of next parm */ -							break; -						} -					} -					temp_len++; -				} -				if (value[temp_len] == 0) { -					options = NULL; -				} else { -					value[temp_len] = 0; -					/* point option to start of next parm */ -					options = value + temp_len + 1; -				} -				/* go from value to value + temp_len condensing -				double commas to singles. Note that this ends up -				allocating a few bytes too many, which is ok */ -				vol->password = kzalloc(temp_len, GFP_KERNEL); -				if (vol->password == NULL) { -					printk(KERN_WARNING "CIFS: no memory " -							    "for password\n"); -					return 1; -				} -				for (i = 0, j = 0; i < temp_len; i++, j++) { -					vol->password[j] = value[i]; -					if (value[i] == separator[0] -						&& value[i+1] == separator[0]) { -						/* skip second comma */ -						i++; -					} -				} -				vol->password[j] = 0; -			} else { -				vol->password = kzalloc(temp_len+1, GFP_KERNEL); -				if (vol->password == NULL) { -					printk(KERN_WARNING "CIFS: no memory " -							    "for password\n"); -					return 1; -				} -				strcpy(vol->password, value); -			} -		} else if (!strnicmp(data, "ip", 2) || -			   !strnicmp(data, "addr", 4)) { -			if (!value || !*value) { -				vol->UNCip = NULL; -			} else if (strnlen(value, INET6_ADDRSTRLEN) < -							INET6_ADDRSTRLEN) { -				vol->UNCip = value; -			} else { -				printk(KERN_WARNING "CIFS: ip address " -						    "too long\n"); -				return 1; -			} -		} else if (strnicmp(data, "sec", 3) == 0) { -			if (!value || !*value) { -				cERROR(1, "no security value specified"); -				continue; -			} else if (strnicmp(value, "krb5i", 5) == 0) { -				vol->secFlg |= CIFSSEC_MAY_KRB5 | -					CIFSSEC_MUST_SIGN; -			} else if (strnicmp(value, "krb5p", 5) == 0) { -				/* vol->secFlg |= CIFSSEC_MUST_SEAL | -					CIFSSEC_MAY_KRB5; */ -				cERROR(1, "Krb5 cifs privacy not supported"); -				return 1; -			} else if (strnicmp(value, "krb5", 4) == 0) { -				vol->secFlg |= CIFSSEC_MAY_KRB5; -#ifdef CONFIG_CIFS_EXPERIMENTAL -			} else if (strnicmp(value, "ntlmsspi", 8) == 0) { -				vol->secFlg |= CIFSSEC_MAY_NTLMSSP | -					CIFSSEC_MUST_SIGN; -			} else if (strnicmp(value, "ntlmssp", 7) == 0) { -				vol->secFlg |= CIFSSEC_MAY_NTLMSSP; -#endif -			} else if (strnicmp(value, "ntlmv2i", 7) == 0) { -				vol->secFlg |= CIFSSEC_MAY_NTLMV2 | -					CIFSSEC_MUST_SIGN; -			} else if (strnicmp(value, "ntlmv2", 6) == 0) { -				vol->secFlg |= CIFSSEC_MAY_NTLMV2; -			} else if (strnicmp(value, "ntlmi", 5) == 0) { -				vol->secFlg |= CIFSSEC_MAY_NTLM | -					CIFSSEC_MUST_SIGN; -			} else if (strnicmp(value, "ntlm", 4) == 0) { -				/* ntlm is default so can be turned off too */ -				vol->secFlg |= CIFSSEC_MAY_NTLM; -			} else if (strnicmp(value, "nontlm", 6) == 0) { -				/* BB is there a better way to do this? */ -				vol->secFlg |= CIFSSEC_MAY_NTLMV2; -#ifdef CONFIG_CIFS_WEAK_PW_HASH -			} else if (strnicmp(value, "lanman", 6) == 0) { -				vol->secFlg |= CIFSSEC_MAY_LANMAN; -#endif -			} else if (strnicmp(value, "none", 4) == 0) { -				vol->nullauth = 1; -			} else { -				cERROR(1, "bad security option: %s", value); -				return 1; -			} -		} else if ((strnicmp(data, "unc", 3) == 0) -			   || (strnicmp(data, "target", 6) == 0) -			   || (strnicmp(data, "path", 4) == 0)) { -			if (!value || !*value) { -				printk(KERN_WARNING "CIFS: invalid path to " -						    "network resource\n"); -				return 1;	/* needs_arg; */ -			} -			if ((temp_len = strnlen(value, 300)) < 300) { -				vol->UNC = kmalloc(temp_len+1, GFP_KERNEL); -				if (vol->UNC == NULL) -					return 1; -				strcpy(vol->UNC, value); -				if (strncmp(vol->UNC, "//", 2) == 0) { -					vol->UNC[0] = '\\'; -					vol->UNC[1] = '\\'; -				} else if (strncmp(vol->UNC, "\\\\", 2) != 0) { -					printk(KERN_WARNING -					       "CIFS: UNC Path does not begin " -					       "with // or \\\\ \n"); -					return 1; -				} -			} else { -				printk(KERN_WARNING "CIFS: UNC name too long\n"); -				return 1; -			} -		} else if ((strnicmp(data, "domain", 3) == 0) -			   || (strnicmp(data, "workgroup", 5) == 0)) { -			if (!value || !*value) { -				printk(KERN_WARNING "CIFS: invalid domain name\n"); -				return 1;	/* needs_arg; */ -			} -			/* BB are there cases in which a comma can be valid in -			a domain name and need special handling? */ -			if (strnlen(value, 256) < 256) { -				vol->domainname = value; -				cFYI(1, "Domain name set"); -			} else { -				printk(KERN_WARNING "CIFS: domain name too " -						    "long\n"); -				return 1; -			} -		} else if (strnicmp(data, "srcaddr", 7) == 0) { -			vol->srcaddr.ss_family = AF_UNSPEC; - -			if (!value || !*value) { -				printk(KERN_WARNING "CIFS: srcaddr value" -				       " not specified.\n"); -				return 1;	/* needs_arg; */ -			} -			i = cifs_convert_address((struct sockaddr *)&vol->srcaddr, -						 value, strlen(value)); -			if (i == 0) { -				printk(KERN_WARNING "CIFS:  Could not parse" -				       " srcaddr: %s\n", -				       value); -				return 1; -			} -		} else if (strnicmp(data, "prefixpath", 10) == 0) { -			if (!value || !*value) { -				printk(KERN_WARNING -					"CIFS: invalid path prefix\n"); -				return 1;       /* needs_argument */ -			} -			if ((temp_len = strnlen(value, 1024)) < 1024) { -				if (value[0] != '/') -					temp_len++;  /* missing leading slash */ -				vol->prepath = kmalloc(temp_len+1, GFP_KERNEL); -				if (vol->prepath == NULL) -					return 1; -				if (value[0] != '/') { -					vol->prepath[0] = '/'; -					strcpy(vol->prepath+1, value); -				} else -					strcpy(vol->prepath, value); -				cFYI(1, "prefix path %s", vol->prepath); -			} else { -				printk(KERN_WARNING "CIFS: prefix too long\n"); -				return 1; -			} -		} else if (strnicmp(data, "iocharset", 9) == 0) { -			if (!value || !*value) { -				printk(KERN_WARNING "CIFS: invalid iocharset " -						    "specified\n"); -				return 1;	/* needs_arg; */ -			} -			if (strnlen(value, 65) < 65) { -				if (strnicmp(value, "default", 7)) -					vol->iocharset = value; -				/* if iocharset not set then load_nls_default -				   is used by caller */ -				cFYI(1, "iocharset set to %s", value); -			} else { -				printk(KERN_WARNING "CIFS: iocharset name " -						    "too long.\n"); -				return 1; -			} -		} else if (!strnicmp(data, "uid", 3) && value && *value) { -			vol->linux_uid = simple_strtoul(value, &value, 0); -			uid_specified = true; -		} else if (!strnicmp(data, "forceuid", 8)) { +			break; +		case Opt_forceuid:  			override_uid = 1; -		} else if (!strnicmp(data, "noforceuid", 10)) { +			break; +		case Opt_noforceuid:  			override_uid = 0; -		} else if (!strnicmp(data, "gid", 3) && value && *value) { -			vol->linux_gid = simple_strtoul(value, &value, 0); -			gid_specified = true; -		} else if (!strnicmp(data, "forcegid", 8)) { +			break; +		case Opt_forcegid:  			override_gid = 1; -		} else if (!strnicmp(data, "noforcegid", 10)) { +			break; +		case Opt_noforcegid:  			override_gid = 0; -		} else if (strnicmp(data, "file_mode", 4) == 0) { -			if (value && *value) { -				vol->file_mode = -					simple_strtoul(value, &value, 0); -			} -		} else if (strnicmp(data, "dir_mode", 4) == 0) { -			if (value && *value) { -				vol->dir_mode = -					simple_strtoul(value, &value, 0); -			} -		} else if (strnicmp(data, "dirmode", 4) == 0) { -			if (value && *value) { -				vol->dir_mode = -					simple_strtoul(value, &value, 0); -			} -		} else if (strnicmp(data, "port", 4) == 0) { -			if (value && *value) { -				vol->port = -					simple_strtoul(value, &value, 0); -			} -		} else if (strnicmp(data, "rsize", 5) == 0) { -			if (value && *value) { -				vol->rsize = -					simple_strtoul(value, &value, 0); -			} -		} else if (strnicmp(data, "wsize", 5) == 0) { -			if (value && *value) { -				vol->wsize = -					simple_strtoul(value, &value, 0); -			} -		} else if (strnicmp(data, "sockopt", 5) == 0) { -			if (!value || !*value) { -				cERROR(1, "no socket option specified"); -				continue; -			} else if (strnicmp(value, "TCP_NODELAY", 11) == 0) { -				vol->sockopt_tcp_nodelay = 1; -			} -		} else if (strnicmp(data, "netbiosname", 4) == 0) { -			if (!value || !*value || (*value == ' ')) { -				cFYI(1, "invalid (empty) netbiosname"); -			} else { -				memset(vol->source_rfc1001_name, 0x20, 15); -				for (i = 0; i < 15; i++) { -				/* BB are there cases in which a comma can be -				valid in this workstation netbios name (and need -				special handling)? */ - -				/* We do not uppercase netbiosname for user */ -					if (value[i] == 0) -						break; -					else -						vol->source_rfc1001_name[i] = -								value[i]; -				} -				/* The string has 16th byte zero still from -				set at top of the function  */ -				if ((i == 15) && (value[i] != 0)) -					printk(KERN_WARNING "CIFS: netbiosname" -						" longer than 15 truncated.\n"); -			} -		} else if (strnicmp(data, "servern", 7) == 0) { -			/* servernetbiosname specified override *SMBSERVER */ -			if (!value || !*value || (*value == ' ')) { -				cFYI(1, "empty server netbiosname specified"); -			} else { -				/* last byte, type, is 0x20 for servr type */ -				memset(vol->target_rfc1001_name, 0x20, 16); - -				for (i = 0; i < 15; i++) { -				/* BB are there cases in which a comma can be -				   valid in this workstation netbios name -				   (and need special handling)? */ - -				/* user or mount helper must uppercase -				   the netbiosname */ -					if (value[i] == 0) -						break; -					else -						vol->target_rfc1001_name[i] = -								value[i]; -				} -				/* The string has 16th byte zero still from -				   set at top of the function  */ -				if ((i == 15) && (value[i] != 0)) -					printk(KERN_WARNING "CIFS: server net" -					"biosname longer than 15 truncated.\n"); -			} -		} else if (strnicmp(data, "credentials", 4) == 0) { -			/* ignore */ -		} else if (strnicmp(data, "version", 3) == 0) { -			/* ignore */ -		} else if (strnicmp(data, "guest", 5) == 0) { -			/* ignore */ -		} else if (strnicmp(data, "rw", 2) == 0) { -			/* ignore */ -		} else if (strnicmp(data, "ro", 2) == 0) { -			/* ignore */ -		} else if (strnicmp(data, "noblocksend", 11) == 0) { +			break; +		case Opt_noblocksend:  			vol->noblocksnd = 1; -		} else if (strnicmp(data, "noautotune", 10) == 0) { +			break; +		case Opt_noautotune:  			vol->noautotune = 1; -		} else if ((strnicmp(data, "suid", 4) == 0) || -				   (strnicmp(data, "nosuid", 6) == 0) || -				   (strnicmp(data, "exec", 4) == 0) || -				   (strnicmp(data, "noexec", 6) == 0) || -				   (strnicmp(data, "nodev", 5) == 0) || -				   (strnicmp(data, "noauto", 6) == 0) || -				   (strnicmp(data, "dev", 3) == 0)) { -			/*  The mount tool or mount.cifs helper (if present) -			    uses these opts to set flags, and the flags are read -			    by the kernel vfs layer before we get here (ie -			    before read super) so there is no point trying to -			    parse these options again and set anything and it -			    is ok to just ignore them */ -			continue; -		} else if (strnicmp(data, "hard", 4) == 0) { +			break; +		case Opt_hard:  			vol->retry = 1; -		} else if (strnicmp(data, "soft", 4) == 0) { +			break; +		case Opt_soft:  			vol->retry = 0; -		} else if (strnicmp(data, "perm", 4) == 0) { +			break; +		case Opt_perm:  			vol->noperm = 0; -		} else if (strnicmp(data, "noperm", 6) == 0) { +			break; +		case Opt_noperm:  			vol->noperm = 1; -		} else if (strnicmp(data, "mapchars", 8) == 0) { +			break; +		case Opt_mapchars:  			vol->remap = 1; -		} else if (strnicmp(data, "nomapchars", 10) == 0) { +			break; +		case Opt_nomapchars:  			vol->remap = 0; -		} else if (strnicmp(data, "sfu", 3) == 0) { +			break; +		case Opt_sfu:  			vol->sfu_emul = 1; -		} else if (strnicmp(data, "nosfu", 5) == 0) { +			break; +		case Opt_nosfu:  			vol->sfu_emul = 0; -		} else if (strnicmp(data, "nodfs", 5) == 0) { +			break; +		case Opt_nodfs:  			vol->nodfs = 1; -		} else if (strnicmp(data, "posixpaths", 10) == 0) { +			break; +		case Opt_posixpaths:  			vol->posix_paths = 1; -		} else if (strnicmp(data, "noposixpaths", 12) == 0) { +			break; +		case Opt_noposixpaths:  			vol->posix_paths = 0; -		} else if (strnicmp(data, "nounix", 6) == 0) { -			vol->no_linux_ext = 1; -		} else if (strnicmp(data, "nolinux", 7) == 0) { +			break; +		case Opt_nounix:  			vol->no_linux_ext = 1; -		} else if ((strnicmp(data, "nocase", 6) == 0) || -			   (strnicmp(data, "ignorecase", 10)  == 0)) { +			break; +		case Opt_nocase:  			vol->nocase = 1; -		} else if (strnicmp(data, "mand", 4) == 0) { -			/* ignore */ -		} else if (strnicmp(data, "nomand", 6) == 0) { -			/* ignore */ -		} else if (strnicmp(data, "_netdev", 7) == 0) { -			/* ignore */ -		} else if (strnicmp(data, "brl", 3) == 0) { +			break; +		case Opt_brl:  			vol->nobrl =  0; -		} else if ((strnicmp(data, "nobrl", 5) == 0) || -			   (strnicmp(data, "nolock", 6) == 0)) { +			break; +		case Opt_nobrl:  			vol->nobrl =  1; -			/* turn off mandatory locking in mode -			if remote locking is turned off since the -			local vfs will do advisory */ +			/* +			 * turn off mandatory locking in mode +			 * if remote locking is turned off since the +			 * local vfs will do advisory +			 */  			if (vol->file_mode ==  				(S_IALLUGO & ~(S_ISUID | S_IXGRP)))  				vol->file_mode = S_IALLUGO; -		} else if (strnicmp(data, "forcemandatorylock", 9) == 0) { -			/* will take the shorter form "forcemand" as well */ -			/* This mount option will force use of mandatory -			  (DOS/Windows style) byte range locks, instead of -			  using posix advisory byte range locks, even if the -			  Unix extensions are available and posix locks would -			  be supported otherwise. If Unix extensions are not -			  negotiated this has no effect since mandatory locks -			  would be used (mandatory locks is all that those -			  those servers support) */ +			break; +		case Opt_forcemandatorylock:  			vol->mand_lock = 1; -		} else if (strnicmp(data, "setuids", 7) == 0) { +			break; +		case Opt_setuids:  			vol->setuids = 1; -		} else if (strnicmp(data, "nosetuids", 9) == 0) { +			break; +		case Opt_nosetuids:  			vol->setuids = 0; -		} else if (strnicmp(data, "dynperm", 7) == 0) { +			break; +		case Opt_dynperm:  			vol->dynperm = true; -		} else if (strnicmp(data, "nodynperm", 9) == 0) { +			break; +		case Opt_nodynperm:  			vol->dynperm = false; -		} else if (strnicmp(data, "nohard", 6) == 0) { +			break; +		case Opt_nohard:  			vol->retry = 0; -		} else if (strnicmp(data, "nosoft", 6) == 0) { +			break; +		case Opt_nosoft:  			vol->retry = 1; -		} else if (strnicmp(data, "nointr", 6) == 0) { +			break; +		case Opt_nointr:  			vol->intr = 0; -		} else if (strnicmp(data, "intr", 4) == 0) { +			break; +		case Opt_intr:  			vol->intr = 1; -		} else if (strnicmp(data, "nostrictsync", 12) == 0) { +			break; +		case Opt_nostrictsync:  			vol->nostrictsync = 1; -		} else if (strnicmp(data, "strictsync", 10) == 0) { +			break; +		case Opt_strictsync:  			vol->nostrictsync = 0; -		} else if (strnicmp(data, "serverino", 7) == 0) { +			break; +		case Opt_serverino:  			vol->server_ino = 1; -		} else if (strnicmp(data, "noserverino", 9) == 0) { +			break; +		case Opt_noserverino:  			vol->server_ino = 0; -		} else if (strnicmp(data, "cifsacl", 7) == 0) { +			break; +		case Opt_rwpidforward: +			vol->rwpidforward = 1; +			break; +		case Opt_cifsacl:  			vol->cifs_acl = 1; -		} else if (strnicmp(data, "nocifsacl", 9) == 0) { +			break; +		case Opt_nocifsacl:  			vol->cifs_acl = 0; -		} else if (strnicmp(data, "acl", 3) == 0) { +			break; +		case Opt_acl:  			vol->no_psx_acl = 0; -		} else if (strnicmp(data, "noacl", 5) == 0) { +			break; +		case Opt_noacl:  			vol->no_psx_acl = 1; -#ifdef CONFIG_CIFS_EXPERIMENTAL -		} else if (strnicmp(data, "locallease", 6) == 0) { +			break; +		case Opt_locallease:  			vol->local_lease = 1; -#endif -		} else if (strnicmp(data, "sign", 4) == 0) { -			vol->secFlg |= CIFSSEC_MUST_SIGN; -		} else if (strnicmp(data, "seal", 4) == 0) { +			break; +		case Opt_sign: +			vol->sign = true; +			break; +		case Opt_seal:  			/* we do not do the following in secFlags because seal -			   is a per tree connection (mount) not a per socket -			   or per-smb connection option in the protocol */ -			/* vol->secFlg |= CIFSSEC_MUST_SEAL; */ +			 * is a per tree connection (mount) not a per socket +			 * or per-smb connection option in the protocol +			 * vol->secFlg |= CIFSSEC_MUST_SEAL; +			 */  			vol->seal = 1; -		} else if (strnicmp(data, "direct", 6) == 0) { -			vol->direct_io = 1; -		} else if (strnicmp(data, "forcedirectio", 13) == 0) { -			vol->direct_io = 1; -		} else if (strnicmp(data, "noac", 4) == 0) { +			break; +		case Opt_noac:  			printk(KERN_WARNING "CIFS: Mount option noac not "  				"supported. Instead set "  				"/proc/fs/cifs/LookupCacheEnabled to 0\n"); -		} else if (strnicmp(data, "fsc", 3) == 0) { +			break; +		case Opt_fsc: +#ifndef CONFIG_CIFS_FSCACHE +			cifs_dbg(VFS, "FS-Cache support needs CONFIG_CIFS_FSCACHE kernel config option set\n"); +			goto cifs_parse_mount_err; +#endif  			vol->fsc = true; -		} else if (strnicmp(data, "mfsymlinks", 10) == 0) { +			break; +		case Opt_mfsymlinks:  			vol->mfsymlinks = true; -		} else if (strnicmp(data, "multiuser", 8) == 0) { +			break; +		case Opt_multiuser:  			vol->multiuser = true; -		} else -			printk(KERN_WARNING "CIFS: Unknown mount option %s\n", -						data); -	} -	if (vol->UNC == NULL) { -		if (devname == NULL) { -			printk(KERN_WARNING "CIFS: Missing UNC name for mount " -						"target\n"); -			return 1; -		} -		if ((temp_len = strnlen(devname, 300)) < 300) { -			vol->UNC = kmalloc(temp_len+1, GFP_KERNEL); -			if (vol->UNC == NULL) -				return 1; -			strcpy(vol->UNC, devname); -			if (strncmp(vol->UNC, "//", 2) == 0) { -				vol->UNC[0] = '\\'; -				vol->UNC[1] = '\\'; -			} else if (strncmp(vol->UNC, "\\\\", 2) != 0) { -				printk(KERN_WARNING "CIFS: UNC Path does not " -						    "begin with // or \\\\ \n"); -				return 1; +			break; +		case Opt_sloppy: +			sloppy = true; +			break; +		case Opt_nosharesock: +			vol->nosharesock = true; +			break; + +		/* Numeric Values */ +		case Opt_backupuid: +			if (get_option_uid(args, &vol->backupuid)) { +				cifs_dbg(VFS, "%s: Invalid backupuid value\n", +					 __func__); +				goto cifs_parse_mount_err;  			} -			value = strpbrk(vol->UNC+2, "/\\"); -			if (value) -				*value = '\\'; -		} else { -			printk(KERN_WARNING "CIFS: UNC name too long\n"); -			return 1; +			vol->backupuid_specified = true; +			break; +		case Opt_backupgid: +			if (get_option_gid(args, &vol->backupgid)) { +				cifs_dbg(VFS, "%s: Invalid backupgid value\n", +					 __func__); +				goto cifs_parse_mount_err; +			} +			vol->backupgid_specified = true; +			break; +		case Opt_uid: +			if (get_option_uid(args, &vol->linux_uid)) { +				cifs_dbg(VFS, "%s: Invalid uid value\n", +					 __func__); +				goto cifs_parse_mount_err; +			} +			uid_specified = true; +			break; +		case Opt_cruid: +			if (get_option_uid(args, &vol->cred_uid)) { +				cifs_dbg(VFS, "%s: Invalid cruid value\n", +					 __func__); +				goto cifs_parse_mount_err; +			} +			break; +		case Opt_gid: +			if (get_option_gid(args, &vol->linux_gid)) { +				cifs_dbg(VFS, "%s: Invalid gid value\n", +					 __func__); +				goto cifs_parse_mount_err; +			} +			gid_specified = true; +			break; +		case Opt_file_mode: +			if (get_option_ul(args, &option)) { +				cifs_dbg(VFS, "%s: Invalid file_mode value\n", +					 __func__); +				goto cifs_parse_mount_err; +			} +			vol->file_mode = option; +			break; +		case Opt_dirmode: +			if (get_option_ul(args, &option)) { +				cifs_dbg(VFS, "%s: Invalid dir_mode value\n", +					 __func__); +				goto cifs_parse_mount_err; +			} +			vol->dir_mode = option; +			break; +		case Opt_port: +			if (get_option_ul(args, &option) || +			    option > USHRT_MAX) { +				cifs_dbg(VFS, "%s: Invalid port value\n", +					 __func__); +				goto cifs_parse_mount_err; +			} +			port = (unsigned short)option; +			break; +		case Opt_rsize: +			if (get_option_ul(args, &option)) { +				cifs_dbg(VFS, "%s: Invalid rsize value\n", +					 __func__); +				goto cifs_parse_mount_err; +			} +			vol->rsize = option; +			break; +		case Opt_wsize: +			if (get_option_ul(args, &option)) { +				cifs_dbg(VFS, "%s: Invalid wsize value\n", +					 __func__); +				goto cifs_parse_mount_err; +			} +			vol->wsize = option; +			break; +		case Opt_actimeo: +			if (get_option_ul(args, &option)) { +				cifs_dbg(VFS, "%s: Invalid actimeo value\n", +					 __func__); +				goto cifs_parse_mount_err; +			} +			vol->actimeo = HZ * option; +			if (vol->actimeo > CIFS_MAX_ACTIMEO) { +				cifs_dbg(VFS, "attribute cache timeout too large\n"); +				goto cifs_parse_mount_err; +			} +			break; + +		/* String Arguments */ + +		case Opt_blank_user: +			/* null user, ie. anonymous authentication */ +			vol->nullauth = 1; +			vol->username = NULL; +			break; +		case Opt_user: +			string = match_strdup(args); +			if (string == NULL) +				goto out_nomem; + +			if (strnlen(string, CIFS_MAX_USERNAME_LEN) > +							CIFS_MAX_USERNAME_LEN) { +				printk(KERN_WARNING "CIFS: username too long\n"); +				goto cifs_parse_mount_err; +			} +			vol->username = kstrdup(string, GFP_KERNEL); +			if (!vol->username) +				goto cifs_parse_mount_err; +			break; +		case Opt_blank_pass: +			/* passwords have to be handled differently +			 * to allow the character used for deliminator +			 * to be passed within them +			 */ + +			/* +			 * Check if this is a case where the  password +			 * starts with a delimiter +			 */ +			tmp_end = strchr(data, '='); +			tmp_end++; +			if (!(tmp_end < end && tmp_end[1] == delim)) { +				/* No it is not. Set the password to NULL */ +				vol->password = NULL; +				break; +			} +			/* Yes it is. Drop down to Opt_pass below.*/ +		case Opt_pass: +			/* Obtain the value string */ +			value = strchr(data, '='); +			value++; + +			/* Set tmp_end to end of the string */ +			tmp_end = (char *) value + strlen(value); + +			/* Check if following character is the deliminator +			 * If yes, we have encountered a double deliminator +			 * reset the NULL character to the deliminator +			 */ +			if (tmp_end < end && tmp_end[1] == delim) { +				tmp_end[0] = delim; + +				/* Keep iterating until we get to a single +				 * deliminator OR the end +				 */ +				while ((tmp_end = strchr(tmp_end, delim)) +					!= NULL && (tmp_end[1] == delim)) { +						tmp_end = (char *) &tmp_end[2]; +				} + +				/* Reset var options to point to next element */ +				if (tmp_end) { +					tmp_end[0] = '\0'; +					options = (char *) &tmp_end[1]; +				} else +					/* Reached the end of the mount option +					 * string */ +					options = end; +			} + +			/* Now build new password string */ +			temp_len = strlen(value); +			vol->password = kzalloc(temp_len+1, GFP_KERNEL); +			if (vol->password == NULL) { +				printk(KERN_WARNING "CIFS: no memory " +						    "for password\n"); +				goto cifs_parse_mount_err; +			} + +			for (i = 0, j = 0; i < temp_len; i++, j++) { +				vol->password[j] = value[i]; +				if ((value[i] == delim) && +				     value[i+1] == delim) +					/* skip the second deliminator */ +					i++; +			} +			vol->password[j] = '\0'; +			break; +		case Opt_blank_ip: +			/* FIXME: should this be an error instead? */ +			got_ip = false; +			break; +		case Opt_ip: +			string = match_strdup(args); +			if (string == NULL) +				goto out_nomem; + +			if (!cifs_convert_address(dstaddr, string, +					strlen(string))) { +				printk(KERN_ERR "CIFS: bad ip= option (%s).\n", +					string); +				goto cifs_parse_mount_err; +			} +			got_ip = true; +			break; +		case Opt_domain: +			string = match_strdup(args); +			if (string == NULL) +				goto out_nomem; + +			if (strnlen(string, CIFS_MAX_DOMAINNAME_LEN) +					== CIFS_MAX_DOMAINNAME_LEN) { +				printk(KERN_WARNING "CIFS: domain name too" +						    " long\n"); +				goto cifs_parse_mount_err; +			} + +			vol->domainname = kstrdup(string, GFP_KERNEL); +			if (!vol->domainname) { +				printk(KERN_WARNING "CIFS: no memory " +						    "for domainname\n"); +				goto cifs_parse_mount_err; +			} +			cifs_dbg(FYI, "Domain name set\n"); +			break; +		case Opt_srcaddr: +			string = match_strdup(args); +			if (string == NULL) +				goto out_nomem; + +			if (!cifs_convert_address( +					(struct sockaddr *)&vol->srcaddr, +					string, strlen(string))) { +				printk(KERN_WARNING "CIFS:  Could not parse" +						    " srcaddr: %s\n", string); +				goto cifs_parse_mount_err; +			} +			break; +		case Opt_iocharset: +			string = match_strdup(args); +			if (string == NULL) +				goto out_nomem; + +			if (strnlen(string, 1024) >= 65) { +				printk(KERN_WARNING "CIFS: iocharset name " +						    "too long.\n"); +				goto cifs_parse_mount_err; +			} + +			 if (strnicmp(string, "default", 7) != 0) { +				vol->iocharset = kstrdup(string, +							 GFP_KERNEL); +				if (!vol->iocharset) { +					printk(KERN_WARNING "CIFS: no memory" +							    "for charset\n"); +					goto cifs_parse_mount_err; +				} +			} +			/* if iocharset not set then load_nls_default +			 * is used by caller +			 */ +			 cifs_dbg(FYI, "iocharset set to %s\n", string); +			break; +		case Opt_netbiosname: +			string = match_strdup(args); +			if (string == NULL) +				goto out_nomem; + +			memset(vol->source_rfc1001_name, 0x20, +				RFC1001_NAME_LEN); +			/* +			 * FIXME: are there cases in which a comma can +			 * be valid in workstation netbios name (and +			 * need special handling)? +			 */ +			for (i = 0; i < RFC1001_NAME_LEN; i++) { +				/* don't ucase netbiosname for user */ +				if (string[i] == 0) +					break; +				vol->source_rfc1001_name[i] = string[i]; +			} +			/* The string has 16th byte zero still from +			 * set at top of the function +			 */ +			if (i == RFC1001_NAME_LEN && string[i] != 0) +				printk(KERN_WARNING "CIFS: netbiosname" +				       " longer than 15 truncated.\n"); + +			break; +		case Opt_servern: +			/* servernetbiosname specified override *SMBSERVER */ +			string = match_strdup(args); +			if (string == NULL) +				goto out_nomem; + +			/* last byte, type, is 0x20 for servr type */ +			memset(vol->target_rfc1001_name, 0x20, +				RFC1001_NAME_LEN_WITH_NULL); + +			/* BB are there cases in which a comma can be +			   valid in this workstation netbios name +			   (and need special handling)? */ + +			/* user or mount helper must uppercase the +			   netbios name */ +			for (i = 0; i < 15; i++) { +				if (string[i] == 0) +					break; +				vol->target_rfc1001_name[i] = string[i]; +			} +			/* The string has 16th byte zero still from +			   set at top of the function  */ +			if (i == RFC1001_NAME_LEN && string[i] != 0) +				printk(KERN_WARNING "CIFS: server net" +				       "biosname longer than 15 truncated.\n"); +			break; +		case Opt_ver: +			string = match_strdup(args); +			if (string == NULL) +				goto out_nomem; + +			if (strnicmp(string, "1", 1) == 0) { +				/* This is the default */ +				break; +			} +			/* For all other value, error */ +			printk(KERN_WARNING "CIFS: Invalid version" +					    " specified\n"); +			goto cifs_parse_mount_err; +		case Opt_vers: +			string = match_strdup(args); +			if (string == NULL) +				goto out_nomem; + +			if (cifs_parse_smb_version(string, vol) != 0) +				goto cifs_parse_mount_err; +			break; +		case Opt_sec: +			string = match_strdup(args); +			if (string == NULL) +				goto out_nomem; + +			if (cifs_parse_security_flavors(string, vol) != 0) +				goto cifs_parse_mount_err; +			break; +		case Opt_cache: +			string = match_strdup(args); +			if (string == NULL) +				goto out_nomem; + +			if (cifs_parse_cache_flavor(string, vol) != 0) +				goto cifs_parse_mount_err; +			break; +		default: +			/* +			 * An option we don't recognize. Save it off for later +			 * if we haven't already found one +			 */ +			if (!invalid) +				invalid = data; +			break;  		} +		/* Free up any allocated string */ +		kfree(string); +		string = NULL;  	} -	if (vol->multiuser && !(vol->secFlg & CIFSSEC_MAY_KRB5)) { -		cERROR(1, "Multiuser mounts currently require krb5 " -			  "authentication!"); -		return 1; +	if (!sloppy && invalid) { +		printk(KERN_ERR "CIFS: Unknown mount option \"%s\"\n", invalid); +		goto cifs_parse_mount_err; +	} + +#ifndef CONFIG_KEYS +	/* Muliuser mounts require CONFIG_KEYS support */ +	if (vol->multiuser) { +		cifs_dbg(VFS, "Multiuser mounts require kernels with CONFIG_KEYS enabled\n"); +		goto cifs_parse_mount_err; +	} +#endif +	if (!vol->UNC) { +		cifs_dbg(VFS, "CIFS mount error: No usable UNC path provided in device string!\n"); +		goto cifs_parse_mount_err; +	} + +	/* make sure UNC has a share name */ +	if (!strchr(vol->UNC + 3, '\\')) { +		cifs_dbg(VFS, "Malformed UNC. Unable to find share name.\n"); +		goto cifs_parse_mount_err; +	} + +	if (!got_ip) { +		/* No ip= option specified? Try to get it from UNC */ +		if (!cifs_convert_address(dstaddr, &vol->UNC[2], +						strlen(&vol->UNC[2]))) { +			printk(KERN_ERR "Unable to determine destination " +					"address.\n"); +			goto cifs_parse_mount_err; +		}  	} -	if (vol->UNCip == NULL) -		vol->UNCip = &vol->UNC[2]; +	/* set the port that we got earlier */ +	cifs_set_port(dstaddr, port);  	if (uid_specified)  		vol->override_uid = override_uid; @@ -1410,7 +1882,15 @@ cifs_parse_mount_options(char *options, const char *devname,  		printk(KERN_NOTICE "CIFS: ignoring forcegid mount option "  				   "specified with no gid= option.\n"); +	kfree(mountdata_copy);  	return 0; + +out_nomem: +	printk(KERN_WARNING "Could not allocate temporary buffer\n"); +cifs_parse_mount_err: +	kfree(string); +	kfree(mountdata_copy); +	return 1;  }  /** Returns true if srcaddr isn't specified and rhs isn't @@ -1430,7 +1910,7 @@ srcip_matches(struct sockaddr *srcaddr, struct sockaddr *rhs)  	}  	case AF_INET6: {  		struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *)srcaddr; -		struct sockaddr_in6 *vaddr6 = (struct sockaddr_in6 *)&rhs; +		struct sockaddr_in6 *vaddr6 = (struct sockaddr_in6 *)rhs;  		return ipv6_addr_equal(&saddr6->sin6_addr, &vaddr6->sin6_addr);  	}  	default: @@ -1439,35 +1919,71 @@ srcip_matches(struct sockaddr *srcaddr, struct sockaddr *rhs)  	}  } +/* + * If no port is specified in addr structure, we try to match with 445 port + * and if it fails - with 139 ports. It should be called only if address + * families of server and addr are equal. + */ +static bool +match_port(struct TCP_Server_Info *server, struct sockaddr *addr) +{ +	__be16 port, *sport; + +	switch (addr->sa_family) { +	case AF_INET: +		sport = &((struct sockaddr_in *) &server->dstaddr)->sin_port; +		port = ((struct sockaddr_in *) addr)->sin_port; +		break; +	case AF_INET6: +		sport = &((struct sockaddr_in6 *) &server->dstaddr)->sin6_port; +		port = ((struct sockaddr_in6 *) addr)->sin6_port; +		break; +	default: +		WARN_ON(1); +		return false; +	} + +	if (!port) { +		port = htons(CIFS_PORT); +		if (port == *sport) +			return true; + +		port = htons(RFC1001_PORT); +	} + +	return port == *sport; +}  static bool  match_address(struct TCP_Server_Info *server, struct sockaddr *addr,  	      struct sockaddr *srcaddr)  { -	struct sockaddr_in *addr4 = (struct sockaddr_in *)addr; -	struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr; -  	switch (addr->sa_family) { -	case AF_INET: -		if (addr4->sin_addr.s_addr != -		    server->addr.sockAddr.sin_addr.s_addr) -			return false; -		if (addr4->sin_port && -		    addr4->sin_port != server->addr.sockAddr.sin_port) +	case AF_INET: { +		struct sockaddr_in *addr4 = (struct sockaddr_in *)addr; +		struct sockaddr_in *srv_addr4 = +					(struct sockaddr_in *)&server->dstaddr; + +		if (addr4->sin_addr.s_addr != srv_addr4->sin_addr.s_addr)  			return false;  		break; -	case AF_INET6: +	} +	case AF_INET6: { +		struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr; +		struct sockaddr_in6 *srv_addr6 = +					(struct sockaddr_in6 *)&server->dstaddr; +  		if (!ipv6_addr_equal(&addr6->sin6_addr, -				     &server->addr.sockAddr6.sin6_addr)) -			return false; -		if (addr6->sin6_scope_id != -		    server->addr.sockAddr6.sin6_scope_id) +				     &srv_addr6->sin6_addr))  			return false; -		if (addr6->sin6_port && -		    addr6->sin6_port != server->addr.sockAddr6.sin6_port) +		if (addr6->sin6_scope_id != srv_addr6->sin6_scope_id)  			return false;  		break;  	} +	default: +		WARN_ON(1); +		return false; /* don't expect to be here */ +	}  	if (!srcip_matches(srcaddr, (struct sockaddr *)&server->srcaddr))  		return false; @@ -1478,68 +1994,64 @@ match_address(struct TCP_Server_Info *server, struct sockaddr *addr,  static bool  match_security(struct TCP_Server_Info *server, struct smb_vol *vol)  { -	unsigned int secFlags; - -	if (vol->secFlg & (~(CIFSSEC_MUST_SIGN | CIFSSEC_MUST_SEAL))) -		secFlags = vol->secFlg; -	else -		secFlags = global_secflags | vol->secFlg; - -	switch (server->secType) { -	case LANMAN: -		if (!(secFlags & (CIFSSEC_MAY_LANMAN|CIFSSEC_MAY_PLNTXT))) -			return false; -		break; -	case NTLMv2: -		if (!(secFlags & CIFSSEC_MAY_NTLMV2)) -			return false; -		break; -	case NTLM: -		if (!(secFlags & CIFSSEC_MAY_NTLM)) -			return false; -		break; -	case Kerberos: -		if (!(secFlags & CIFSSEC_MAY_KRB5)) -			return false; -		break; -	case RawNTLMSSP: -		if (!(secFlags & CIFSSEC_MAY_NTLMSSP)) -			return false; -		break; -	default: -		/* shouldn't happen */ +	/* +	 * The select_sectype function should either return the vol->sectype +	 * that was specified, or "Unspecified" if that sectype was not +	 * compatible with the given NEGOTIATE request. +	 */ +	if (select_sectype(server, vol->sectype) == Unspecified)  		return false; -	} -	/* now check if signing mode is acceptible */ -	if ((secFlags & CIFSSEC_MAY_SIGN) == 0 && -	    (server->secMode & SECMODE_SIGN_REQUIRED)) -			return false; -	else if (((secFlags & CIFSSEC_MUST_SIGN) == CIFSSEC_MUST_SIGN) && -		 (server->secMode & -		  (SECMODE_SIGN_ENABLED|SECMODE_SIGN_REQUIRED)) == 0) -			return false; +	/* +	 * Now check if signing mode is acceptable. No need to check +	 * global_secflags at this point since if MUST_SIGN is set then +	 * the server->sign had better be too. +	 */ +	if (vol->sign && !server->sign) +		return false;  	return true;  } +static int match_server(struct TCP_Server_Info *server, struct smb_vol *vol) +{ +	struct sockaddr *addr = (struct sockaddr *)&vol->dstaddr; + +	if (vol->nosharesock) +		return 0; + +	if ((server->vals != vol->vals) || (server->ops != vol->ops)) +		return 0; + +	if (!net_eq(cifs_net_ns(server), current->nsproxy->net_ns)) +		return 0; + +	if (!match_address(server, addr, +			   (struct sockaddr *)&vol->srcaddr)) +		return 0; + +	if (!match_port(server, addr)) +		return 0; + +	if (!match_security(server, vol)) +		return 0; + +	return 1; +} +  static struct TCP_Server_Info * -cifs_find_tcp_session(struct sockaddr *addr, struct smb_vol *vol) +cifs_find_tcp_session(struct smb_vol *vol)  {  	struct TCP_Server_Info *server;  	spin_lock(&cifs_tcp_ses_lock);  	list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) { -		if (!match_address(server, addr, -				   (struct sockaddr *)&vol->srcaddr)) -			continue; - -		if (!match_security(server, vol)) +		if (!match_server(server, vol))  			continue;  		++server->srv_count;  		spin_unlock(&cifs_tcp_ses_lock); -		cFYI(1, "Existing tcp session with server found"); +		cifs_dbg(FYI, "Existing tcp session with server found\n");  		return server;  	}  	spin_unlock(&cifs_tcp_ses_lock); @@ -1557,9 +2069,13 @@ cifs_put_tcp_session(struct TCP_Server_Info *server)  		return;  	} +	put_net(cifs_net_ns(server)); +  	list_del_init(&server->tcp_ses_list);  	spin_unlock(&cifs_tcp_ses_lock); +	cancel_delayed_work_sync(&server->echo); +  	spin_lock(&GlobalMid_Lock);  	server->tcpStatus = CifsExiting;  	spin_unlock(&GlobalMid_Lock); @@ -1580,40 +2096,12 @@ static struct TCP_Server_Info *  cifs_get_tcp_session(struct smb_vol *volume_info)  {  	struct TCP_Server_Info *tcp_ses = NULL; -	struct sockaddr_storage addr; -	struct sockaddr_in *sin_server = (struct sockaddr_in *) &addr; -	struct sockaddr_in6 *sin_server6 = (struct sockaddr_in6 *) &addr;  	int rc; -	memset(&addr, 0, sizeof(struct sockaddr_storage)); - -	cFYI(1, "UNC: %s ip: %s", volume_info->UNC, volume_info->UNCip); - -	if (volume_info->UNCip && volume_info->UNC) { -		rc = cifs_fill_sockaddr((struct sockaddr *)&addr, -					volume_info->UNCip, -					strlen(volume_info->UNCip), -					volume_info->port); -		if (!rc) { -			/* we failed translating address */ -			rc = -EINVAL; -			goto out_err; -		} -	} else if (volume_info->UNCip) { -		/* BB using ip addr as tcp_ses name to connect to the -		   DFS root below */ -		cERROR(1, "Connecting to DFS root not implemented yet"); -		rc = -EINVAL; -		goto out_err; -	} else /* which tcp_sess DFS root would we conect to */ { -		cERROR(1, "CIFS mount error: No UNC path (e.g. -o " -			"unc=//192.168.1.100/public) specified"); -		rc = -EINVAL; -		goto out_err; -	} +	cifs_dbg(FYI, "UNC: %s\n", volume_info->UNC);  	/* see if we already have a matching tcp_ses */ -	tcp_ses = cifs_find_tcp_session((struct sockaddr *)&addr, volume_info); +	tcp_ses = cifs_find_tcp_session(volume_info);  	if (tcp_ses)  		return tcp_ses; @@ -1623,12 +2111,9 @@ cifs_get_tcp_session(struct smb_vol *volume_info)  		goto out_err;  	} -	rc = cifs_crypto_shash_allocate(tcp_ses); -	if (rc) { -		cERROR(1, "could not setup hash structures rc %d", rc); -		goto out_err; -	} - +	tcp_ses->ops = volume_info->ops; +	tcp_ses->vals = volume_info->vals; +	cifs_set_net_ns(tcp_ses, get_net(current->nsproxy->net_ns));  	tcp_ses->hostname = extract_hostname(volume_info->UNC);  	if (IS_ERR(tcp_ses->hostname)) {  		rc = PTR_ERR(tcp_ses->hostname); @@ -1638,7 +2123,8 @@ cifs_get_tcp_session(struct smb_vol *volume_info)  	tcp_ses->noblocksnd = volume_info->noblocksnd;  	tcp_ses->noautotune = volume_info->noautotune;  	tcp_ses->tcp_nodelay = volume_info->sockopt_tcp_nodelay; -	atomic_set(&tcp_ses->inFlight, 0); +	tcp_ses->in_flight = 0; +	tcp_ses->credits = 1;  	init_waitqueue_head(&tcp_ses->response_q);  	init_waitqueue_head(&tcp_ses->request_q);  	INIT_LIST_HEAD(&tcp_ses->pending_mid_q); @@ -1649,33 +2135,29 @@ cifs_get_tcp_session(struct smb_vol *volume_info)  		volume_info->target_rfc1001_name, RFC1001_NAME_LEN_WITH_NULL);  	tcp_ses->session_estab = false;  	tcp_ses->sequence_number = 0; +	tcp_ses->lstrp = jiffies; +	spin_lock_init(&tcp_ses->req_lock);  	INIT_LIST_HEAD(&tcp_ses->tcp_ses_list);  	INIT_LIST_HEAD(&tcp_ses->smb_ses_list); - +	INIT_DELAYED_WORK(&tcp_ses->echo, cifs_echo_request); +	memcpy(&tcp_ses->srcaddr, &volume_info->srcaddr, +	       sizeof(tcp_ses->srcaddr)); +	memcpy(&tcp_ses->dstaddr, &volume_info->dstaddr, +		sizeof(tcp_ses->dstaddr)); +#ifdef CONFIG_CIFS_SMB2 +	get_random_bytes(tcp_ses->client_guid, SMB2_CLIENT_GUID_SIZE); +#endif  	/*  	 * at this point we are the only ones with the pointer  	 * to the struct since the kernel thread not created yet  	 * no need to spinlock this init of tcpStatus or srv_count  	 */  	tcp_ses->tcpStatus = CifsNew; -	memcpy(&tcp_ses->srcaddr, &volume_info->srcaddr, -	       sizeof(tcp_ses->srcaddr));  	++tcp_ses->srv_count; -	if (addr.ss_family == AF_INET6) { -		cFYI(1, "attempting ipv6 connect"); -		/* BB should we allow ipv6 on port 139? */ -		/* other OS never observed in Wild doing 139 with v6 */ -		memcpy(&tcp_ses->addr.sockAddr6, sin_server6, -			sizeof(struct sockaddr_in6)); -		rc = ipv6_connect(tcp_ses); -	} else { -		memcpy(&tcp_ses->addr.sockAddr, sin_server, -			sizeof(struct sockaddr_in)); -		rc = ipv4_connect(tcp_ses); -	} +	rc = ip_connect(tcp_ses);  	if (rc < 0) { -		cERROR(1, "Error connecting to socket. Aborting operation"); +		cifs_dbg(VFS, "Error connecting to socket. Aborting operation.\n");  		goto out_err_crypto_release;  	} @@ -1684,14 +2166,15 @@ cifs_get_tcp_session(struct smb_vol *volume_info)  	 * this will succeed. No need for try_module_get().  	 */  	__module_get(THIS_MODULE); -	tcp_ses->tsk = kthread_run((void *)(void *)cifs_demultiplex_thread, +	tcp_ses->tsk = kthread_run(cifs_demultiplex_thread,  				  tcp_ses, "cifsd");  	if (IS_ERR(tcp_ses->tsk)) {  		rc = PTR_ERR(tcp_ses->tsk); -		cERROR(1, "error %d create cifsd thread", rc); +		cifs_dbg(VFS, "error %d create cifsd thread\n", rc);  		module_put(THIS_MODULE);  		goto out_err_crypto_release;  	} +	tcp_ses->tcpStatus = CifsNeedNegotiate;  	/* thread spawned, put it on the list */  	spin_lock(&cifs_tcp_ses_lock); @@ -1700,11 +2183,16 @@ cifs_get_tcp_session(struct smb_vol *volume_info)  	cifs_fscache_get_client_cookie(tcp_ses); +	/* queue echo request delayed work */ +	queue_delayed_work(cifsiod_wq, &tcp_ses->echo, SMB_ECHO_INTERVAL); +  	return tcp_ses;  out_err_crypto_release:  	cifs_crypto_shash_release(tcp_ses); +	put_net(cifs_net_ns(tcp_ses)); +  out_err:  	if (tcp_ses) {  		if (!IS_ERR(tcp_ses->hostname)) @@ -1716,30 +2204,51 @@ out_err:  	return ERR_PTR(rc);  } -static struct cifsSesInfo * +static int match_session(struct cifs_ses *ses, struct smb_vol *vol) +{ +	if (vol->sectype != Unspecified && +	    vol->sectype != ses->sectype) +		return 0; + +	switch (ses->sectype) { +	case Kerberos: +		if (!uid_eq(vol->cred_uid, ses->cred_uid)) +			return 0; +		break; +	default: +		/* NULL username means anonymous session */ +		if (ses->user_name == NULL) { +			if (!vol->nullauth) +				return 0; +			break; +		} + +		/* anything else takes username/password */ +		if (strncmp(ses->user_name, +			    vol->username ? vol->username : "", +			    CIFS_MAX_USERNAME_LEN)) +			return 0; +		if ((vol->username && strlen(vol->username) != 0) && +		    ses->password != NULL && +		    strncmp(ses->password, +			    vol->password ? vol->password : "", +			    CIFS_MAX_PASSWORD_LEN)) +			return 0; +	} +	return 1; +} + +static struct cifs_ses *  cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb_vol *vol)  { -	struct cifsSesInfo *ses; +	struct cifs_ses *ses;  	spin_lock(&cifs_tcp_ses_lock);  	list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { -		switch (server->secType) { -		case Kerberos: -			if (vol->cred_uid != ses->cred_uid) -				continue; -			break; -		default: -			/* anything else takes username/password */ -			if (strncmp(ses->userName, vol->username, -				    MAX_USERNAME_SIZE)) -				continue; -			if (strlen(vol->username) != 0 && -			    ses->password != NULL && -			    strncmp(ses->password, -				    vol->password ? vol->password : "", -				    MAX_PASSWORD_SIZE)) -				continue; -		} +		if (ses->status == CifsExiting) +			continue; +		if (!match_session(ses, vol)) +			continue;  		++ses->ses_count;  		spin_unlock(&cifs_tcp_ses_lock);  		return ses; @@ -1749,41 +2258,188 @@ cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb_vol *vol)  }  static void -cifs_put_smb_ses(struct cifsSesInfo *ses) +cifs_put_smb_ses(struct cifs_ses *ses)  { -	int xid; +	unsigned int rc, xid;  	struct TCP_Server_Info *server = ses->server; -	cFYI(1, "%s: ses_count=%d\n", __func__, ses->ses_count); +	cifs_dbg(FYI, "%s: ses_count=%d\n", __func__, ses->ses_count); +  	spin_lock(&cifs_tcp_ses_lock); +	if (ses->status == CifsExiting) { +		spin_unlock(&cifs_tcp_ses_lock); +		return; +	}  	if (--ses->ses_count > 0) {  		spin_unlock(&cifs_tcp_ses_lock);  		return;  	} +	if (ses->status == CifsGood) +		ses->status = CifsExiting; +	spin_unlock(&cifs_tcp_ses_lock); +	if (ses->status == CifsExiting && server->ops->logoff) { +		xid = get_xid(); +		rc = server->ops->logoff(xid, ses); +		if (rc) +			cifs_dbg(VFS, "%s: Session Logoff failure rc=%d\n", +				__func__, rc); +		_free_xid(xid); +	} + +	spin_lock(&cifs_tcp_ses_lock);  	list_del_init(&ses->smb_ses_list);  	spin_unlock(&cifs_tcp_ses_lock); -	if (ses->status == CifsGood) { -		xid = GetXid(); -		CIFSSMBLogoff(xid, ses); -		_FreeXid(xid); -	}  	sesInfoFree(ses);  	cifs_put_tcp_session(server);  } -static struct cifsSesInfo * +#ifdef CONFIG_KEYS + +/* strlen("cifs:a:") + CIFS_MAX_DOMAINNAME_LEN + 1 */ +#define CIFSCREDS_DESC_SIZE (7 + CIFS_MAX_DOMAINNAME_LEN + 1) + +/* Populate username and pw fields from keyring if possible */ +static int +cifs_set_cifscreds(struct smb_vol *vol, struct cifs_ses *ses) +{ +	int rc = 0; +	char *desc, *delim, *payload; +	ssize_t len; +	struct key *key; +	struct TCP_Server_Info *server = ses->server; +	struct sockaddr_in *sa; +	struct sockaddr_in6 *sa6; +	struct user_key_payload *upayload; + +	desc = kmalloc(CIFSCREDS_DESC_SIZE, GFP_KERNEL); +	if (!desc) +		return -ENOMEM; + +	/* try to find an address key first */ +	switch (server->dstaddr.ss_family) { +	case AF_INET: +		sa = (struct sockaddr_in *)&server->dstaddr; +		sprintf(desc, "cifs:a:%pI4", &sa->sin_addr.s_addr); +		break; +	case AF_INET6: +		sa6 = (struct sockaddr_in6 *)&server->dstaddr; +		sprintf(desc, "cifs:a:%pI6c", &sa6->sin6_addr.s6_addr); +		break; +	default: +		cifs_dbg(FYI, "Bad ss_family (%hu)\n", +			 server->dstaddr.ss_family); +		rc = -EINVAL; +		goto out_err; +	} + +	cifs_dbg(FYI, "%s: desc=%s\n", __func__, desc); +	key = request_key(&key_type_logon, desc, ""); +	if (IS_ERR(key)) { +		if (!ses->domainName) { +			cifs_dbg(FYI, "domainName is NULL\n"); +			rc = PTR_ERR(key); +			goto out_err; +		} + +		/* didn't work, try to find a domain key */ +		sprintf(desc, "cifs:d:%s", ses->domainName); +		cifs_dbg(FYI, "%s: desc=%s\n", __func__, desc); +		key = request_key(&key_type_logon, desc, ""); +		if (IS_ERR(key)) { +			rc = PTR_ERR(key); +			goto out_err; +		} +	} + +	down_read(&key->sem); +	upayload = key->payload.data; +	if (IS_ERR_OR_NULL(upayload)) { +		rc = upayload ? PTR_ERR(upayload) : -EINVAL; +		goto out_key_put; +	} + +	/* find first : in payload */ +	payload = (char *)upayload->data; +	delim = strnchr(payload, upayload->datalen, ':'); +	cifs_dbg(FYI, "payload=%s\n", payload); +	if (!delim) { +		cifs_dbg(FYI, "Unable to find ':' in payload (datalen=%d)\n", +			 upayload->datalen); +		rc = -EINVAL; +		goto out_key_put; +	} + +	len = delim - payload; +	if (len > CIFS_MAX_USERNAME_LEN || len <= 0) { +		cifs_dbg(FYI, "Bad value from username search (len=%zd)\n", +			 len); +		rc = -EINVAL; +		goto out_key_put; +	} + +	vol->username = kstrndup(payload, len, GFP_KERNEL); +	if (!vol->username) { +		cifs_dbg(FYI, "Unable to allocate %zd bytes for username\n", +			 len); +		rc = -ENOMEM; +		goto out_key_put; +	} +	cifs_dbg(FYI, "%s: username=%s\n", __func__, vol->username); + +	len = key->datalen - (len + 1); +	if (len > CIFS_MAX_PASSWORD_LEN || len <= 0) { +		cifs_dbg(FYI, "Bad len for password search (len=%zd)\n", len); +		rc = -EINVAL; +		kfree(vol->username); +		vol->username = NULL; +		goto out_key_put; +	} + +	++delim; +	vol->password = kstrndup(delim, len, GFP_KERNEL); +	if (!vol->password) { +		cifs_dbg(FYI, "Unable to allocate %zd bytes for password\n", +			 len); +		rc = -ENOMEM; +		kfree(vol->username); +		vol->username = NULL; +		goto out_key_put; +	} + +out_key_put: +	up_read(&key->sem); +	key_put(key); +out_err: +	kfree(desc); +	cifs_dbg(FYI, "%s: returning %d\n", __func__, rc); +	return rc; +} +#else /* ! CONFIG_KEYS */ +static inline int +cifs_set_cifscreds(struct smb_vol *vol __attribute__((unused)), +		   struct cifs_ses *ses __attribute__((unused))) +{ +	return -ENOSYS; +} +#endif /* CONFIG_KEYS */ + +static struct cifs_ses *  cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)  { -	int rc = -ENOMEM, xid; -	struct cifsSesInfo *ses; +	int rc = -ENOMEM; +	unsigned int xid; +	struct cifs_ses *ses; +	struct sockaddr_in *addr = (struct sockaddr_in *)&server->dstaddr; +	struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&server->dstaddr; -	xid = GetXid(); +	xid = get_xid();  	ses = cifs_find_smb_ses(server, volume_info);  	if (ses) { -		cFYI(1, "Existing smb sess found (status=%d)", ses->status); +		cifs_dbg(FYI, "Existing smb sess found (status=%d)\n", +			 ses->status);  		mutex_lock(&ses->session_mutex);  		rc = cifs_negotiate_protocol(xid, ses); @@ -1791,18 +2447,18 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)  			mutex_unlock(&ses->session_mutex);  			/* problem -- put our ses reference */  			cifs_put_smb_ses(ses); -			FreeXid(xid); +			free_xid(xid);  			return ERR_PTR(rc);  		}  		if (ses->need_reconnect) { -			cFYI(1, "Session needs reconnect"); +			cifs_dbg(FYI, "Session needs reconnect\n");  			rc = cifs_setup_session(xid, ses,  						volume_info->local_nls);  			if (rc) {  				mutex_unlock(&ses->session_mutex);  				/* problem -- put our reference */  				cifs_put_smb_ses(ses); -				FreeXid(xid); +				free_xid(xid);  				return ERR_PTR(rc);  			}  		} @@ -1810,27 +2466,27 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)  		/* existing SMB ses has a server reference already */  		cifs_put_tcp_session(server); -		FreeXid(xid); +		free_xid(xid);  		return ses;  	} -	cFYI(1, "Existing smb sess not found"); +	cifs_dbg(FYI, "Existing smb sess not found\n");  	ses = sesInfoAlloc();  	if (ses == NULL)  		goto get_ses_fail;  	/* new SMB session uses our server ref */  	ses->server = server; -	if (server->addr.sockAddr6.sin6_family == AF_INET6) -		sprintf(ses->serverName, "%pI6", -			&server->addr.sockAddr6.sin6_addr); +	if (server->dstaddr.ss_family == AF_INET6) +		sprintf(ses->serverName, "%pI6", &addr6->sin6_addr);  	else -		sprintf(ses->serverName, "%pI4", -			&server->addr.sockAddr.sin_addr.s_addr); +		sprintf(ses->serverName, "%pI4", &addr->sin_addr); -	if (volume_info->username) -		strncpy(ses->userName, volume_info->username, -			MAX_USERNAME_SIZE); +	if (volume_info->username) { +		ses->user_name = kstrdup(volume_info->username, GFP_KERNEL); +		if (!ses->user_name) +			goto get_ses_fail; +	}  	/* volume_info->password freed at unmount */  	if (volume_info->password) { @@ -1845,7 +2501,9 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)  	}  	ses->cred_uid = volume_info->cred_uid;  	ses->linux_uid = volume_info->linux_uid; -	ses->overrideSecFlg = volume_info->secFlg; + +	ses->sectype = volume_info->sectype; +	ses->sign = volume_info->sign;  	mutex_lock(&ses->session_mutex);  	rc = cifs_negotiate_protocol(xid, ses); @@ -1860,29 +2518,35 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)  	list_add(&ses->smb_ses_list, &server->smb_ses_list);  	spin_unlock(&cifs_tcp_ses_lock); -	FreeXid(xid); +	free_xid(xid);  	return ses;  get_ses_fail:  	sesInfoFree(ses); -	FreeXid(xid); +	free_xid(xid);  	return ERR_PTR(rc);  } -static struct cifsTconInfo * -cifs_find_tcon(struct cifsSesInfo *ses, const char *unc) +static int match_tcon(struct cifs_tcon *tcon, const char *unc) +{ +	if (tcon->tidStatus == CifsExiting) +		return 0; +	if (strncmp(tcon->treeName, unc, MAX_TREE_SIZE)) +		return 0; +	return 1; +} + +static struct cifs_tcon * +cifs_find_tcon(struct cifs_ses *ses, const char *unc)  {  	struct list_head *tmp; -	struct cifsTconInfo *tcon; +	struct cifs_tcon *tcon;  	spin_lock(&cifs_tcp_ses_lock);  	list_for_each(tmp, &ses->tcon_list) { -		tcon = list_entry(tmp, struct cifsTconInfo, tcon_list); -		if (tcon->tidStatus == CifsExiting) -			continue; -		if (strncmp(tcon->treeName, unc, MAX_TREE_SIZE)) +		tcon = list_entry(tmp, struct cifs_tcon, tcon_list); +		if (!match_tcon(tcon, unc))  			continue; -  		++tcon->tc_count;  		spin_unlock(&cifs_tcp_ses_lock);  		return tcon; @@ -1892,12 +2556,12 @@ cifs_find_tcon(struct cifsSesInfo *ses, const char *unc)  }  static void -cifs_put_tcon(struct cifsTconInfo *tcon) +cifs_put_tcon(struct cifs_tcon *tcon)  { -	int xid; -	struct cifsSesInfo *ses = tcon->ses; +	unsigned int xid; +	struct cifs_ses *ses = tcon->ses; -	cFYI(1, "%s: tc_count=%d\n", __func__, tcon->tc_count); +	cifs_dbg(FYI, "%s: tc_count=%d\n", __func__, tcon->tc_count);  	spin_lock(&cifs_tcp_ses_lock);  	if (--tcon->tc_count > 0) {  		spin_unlock(&cifs_tcp_ses_lock); @@ -1907,32 +2571,37 @@ cifs_put_tcon(struct cifsTconInfo *tcon)  	list_del_init(&tcon->tcon_list);  	spin_unlock(&cifs_tcp_ses_lock); -	xid = GetXid(); -	CIFSSMBTDis(xid, tcon); -	_FreeXid(xid); +	xid = get_xid(); +	if (ses->server->ops->tree_disconnect) +		ses->server->ops->tree_disconnect(xid, tcon); +	_free_xid(xid);  	cifs_fscache_release_super_cookie(tcon);  	tconInfoFree(tcon);  	cifs_put_smb_ses(ses);  } -static struct cifsTconInfo * -cifs_get_tcon(struct cifsSesInfo *ses, struct smb_vol *volume_info) +static struct cifs_tcon * +cifs_get_tcon(struct cifs_ses *ses, struct smb_vol *volume_info)  {  	int rc, xid; -	struct cifsTconInfo *tcon; +	struct cifs_tcon *tcon;  	tcon = cifs_find_tcon(ses, volume_info->UNC);  	if (tcon) { -		cFYI(1, "Found match on UNC path"); +		cifs_dbg(FYI, "Found match on UNC path\n");  		/* existing tcon already has a reference */  		cifs_put_smb_ses(ses);  		if (tcon->seal != volume_info->seal) -			cERROR(1, "transport encryption setting " -				   "conflicts with existing tid"); +			cifs_dbg(VFS, "transport encryption setting conflicts with existing tid\n");  		return tcon;  	} +	if (!ses->server->ops->tree_connect) { +		rc = -ENOSYS; +		goto out_fail; +	} +  	tcon = tconInfoAlloc();  	if (tcon == NULL) {  		rc = -ENOMEM; @@ -1948,35 +2617,32 @@ cifs_get_tcon(struct cifsSesInfo *ses, struct smb_vol *volume_info)  		}  	} -	if (strchr(volume_info->UNC + 3, '\\') == NULL -	    && strchr(volume_info->UNC + 3, '/') == NULL) { -		cERROR(1, "Missing share name"); -		rc = -ENODEV; -		goto out_fail; -	} - -	/* BB Do we need to wrap session_mutex around -	 * this TCon call and Unix SetFS as -	 * we do on SessSetup and reconnect? */ -	xid = GetXid(); -	rc = CIFSTCon(xid, ses, volume_info->UNC, tcon, volume_info->local_nls); -	FreeXid(xid); -	cFYI(1, "CIFS Tcon rc = %d", rc); +	/* +	 * BB Do we need to wrap session_mutex around this TCon call and Unix +	 * SetFS as we do on SessSetup and reconnect? +	 */ +	xid = get_xid(); +	rc = ses->server->ops->tree_connect(xid, ses, volume_info->UNC, tcon, +					    volume_info->local_nls); +	free_xid(xid); +	cifs_dbg(FYI, "Tcon rc = %d\n", rc);  	if (rc)  		goto out_fail;  	if (volume_info->nodfs) {  		tcon->Flags &= ~SMB_SHARE_IS_IN_DFS; -		cFYI(1, "DFS disabled (%d)", tcon->Flags); +		cifs_dbg(FYI, "DFS disabled (%d)\n", tcon->Flags);  	}  	tcon->seal = volume_info->seal; -	/* we can have only one retry value for a connection -	   to a share so for resources mounted more than once -	   to the same server share the last value passed in -	   for the retry flag is used */ +	/* +	 * We can have only one retry value for a connection to a share so for +	 * resources mounted more than once to the same server share the last +	 * value passed in for the retry flag is used. +	 */  	tcon->retry = volume_info->retry;  	tcon->nocase = volume_info->nocase;  	tcon->local_lease = volume_info->local_lease; +	INIT_LIST_HEAD(&tcon->pending_opens);  	spin_lock(&cifs_tcp_ses_lock);  	list_add(&tcon->tcon_list, &ses->tcon_list); @@ -2009,38 +2675,127 @@ cifs_put_tlink(struct tcon_link *tlink)  	return;  } +static inline struct tcon_link * +cifs_sb_master_tlink(struct cifs_sb_info *cifs_sb) +{ +	return cifs_sb->master_tlink; +} + +static int +compare_mount_options(struct super_block *sb, struct cifs_mnt_data *mnt_data) +{ +	struct cifs_sb_info *old = CIFS_SB(sb); +	struct cifs_sb_info *new = mnt_data->cifs_sb; + +	if ((sb->s_flags & CIFS_MS_MASK) != (mnt_data->flags & CIFS_MS_MASK)) +		return 0; + +	if ((old->mnt_cifs_flags & CIFS_MOUNT_MASK) != +	    (new->mnt_cifs_flags & CIFS_MOUNT_MASK)) +		return 0; + +	/* +	 * We want to share sb only if we don't specify an r/wsize or +	 * specified r/wsize is greater than or equal to existing one. +	 */ +	if (new->wsize && new->wsize < old->wsize) +		return 0; + +	if (new->rsize && new->rsize < old->rsize) +		return 0; + +	if (!uid_eq(old->mnt_uid, new->mnt_uid) || !gid_eq(old->mnt_gid, new->mnt_gid)) +		return 0; + +	if (old->mnt_file_mode != new->mnt_file_mode || +	    old->mnt_dir_mode != new->mnt_dir_mode) +		return 0; + +	if (strcmp(old->local_nls->charset, new->local_nls->charset)) +		return 0; + +	if (old->actimeo != new->actimeo) +		return 0; + +	return 1; +} + +int +cifs_match_super(struct super_block *sb, void *data) +{ +	struct cifs_mnt_data *mnt_data = (struct cifs_mnt_data *)data; +	struct smb_vol *volume_info; +	struct cifs_sb_info *cifs_sb; +	struct TCP_Server_Info *tcp_srv; +	struct cifs_ses *ses; +	struct cifs_tcon *tcon; +	struct tcon_link *tlink; +	int rc = 0; + +	spin_lock(&cifs_tcp_ses_lock); +	cifs_sb = CIFS_SB(sb); +	tlink = cifs_get_tlink(cifs_sb_master_tlink(cifs_sb)); +	if (IS_ERR(tlink)) { +		spin_unlock(&cifs_tcp_ses_lock); +		return rc; +	} +	tcon = tlink_tcon(tlink); +	ses = tcon->ses; +	tcp_srv = ses->server; + +	volume_info = mnt_data->vol; + +	if (!match_server(tcp_srv, volume_info) || +	    !match_session(ses, volume_info) || +	    !match_tcon(tcon, volume_info->UNC)) { +		rc = 0; +		goto out; +	} + +	rc = compare_mount_options(sb, mnt_data); +out: +	spin_unlock(&cifs_tcp_ses_lock); +	cifs_put_tlink(tlink); +	return rc; +} +  int -get_dfs_path(int xid, struct cifsSesInfo *pSesInfo, const char *old_path, -	     const struct nls_table *nls_codepage, unsigned int *pnum_referrals, -	     struct dfs_info3_param **preferrals, int remap) +get_dfs_path(const unsigned int xid, struct cifs_ses *ses, const char *old_path, +	     const struct nls_table *nls_codepage, unsigned int *num_referrals, +	     struct dfs_info3_param **referrals, int remap)  {  	char *temp_unc;  	int rc = 0; -	*pnum_referrals = 0; -	*preferrals = NULL; +	if (!ses->server->ops->tree_connect || !ses->server->ops->get_dfs_refer) +		return -ENOSYS; + +	*num_referrals = 0; +	*referrals = NULL; -	if (pSesInfo->ipc_tid == 0) { +	if (ses->ipc_tid == 0) {  		temp_unc = kmalloc(2 /* for slashes */ + -			strnlen(pSesInfo->serverName, -				SERVER_NAME_LEN_WITH_NULL * 2) -				 + 1 + 4 /* slash IPC$ */  + 2, -				GFP_KERNEL); +			strnlen(ses->serverName, SERVER_NAME_LEN_WITH_NULL * 2) +				+ 1 + 4 /* slash IPC$ */ + 2, GFP_KERNEL);  		if (temp_unc == NULL)  			return -ENOMEM;  		temp_unc[0] = '\\';  		temp_unc[1] = '\\'; -		strcpy(temp_unc + 2, pSesInfo->serverName); -		strcpy(temp_unc + 2 + strlen(pSesInfo->serverName), "\\IPC$"); -		rc = CIFSTCon(xid, pSesInfo, temp_unc, NULL, nls_codepage); -		cFYI(1, "CIFS Tcon rc = %d ipc_tid = %d", rc, pSesInfo->ipc_tid); +		strcpy(temp_unc + 2, ses->serverName); +		strcpy(temp_unc + 2 + strlen(ses->serverName), "\\IPC$"); +		rc = ses->server->ops->tree_connect(xid, ses, temp_unc, NULL, +						    nls_codepage); +		cifs_dbg(FYI, "Tcon rc = %d ipc_tid = %d\n", rc, ses->ipc_tid);  		kfree(temp_unc);  	}  	if (rc == 0) -		rc = CIFSGetDFSRefer(xid, pSesInfo, old_path, preferrals, -				     pnum_referrals, nls_codepage, remap); -	/* BB map targetUNCs to dfs_info3 structures, here or -		in CIFSGetDFSRefer BB */ +		rc = ses->server->ops->get_dfs_refer(xid, ses, old_path, +						     referrals, num_referrals, +						     nls_codepage, remap); +	/* +	 * BB - map targetUNCs to dfs_info3 structures, here or in +	 * ses->server->ops->get_dfs_refer. +	 */  	return rc;  } @@ -2108,96 +2863,138 @@ bind_socket(struct TCP_Server_Info *server)  			saddr4 = (struct sockaddr_in *)&server->srcaddr;  			saddr6 = (struct sockaddr_in6 *)&server->srcaddr;  			if (saddr6->sin6_family == AF_INET6) -				cERROR(1, "cifs: " -				       "Failed to bind to: %pI6c, error: %d\n", -				       &saddr6->sin6_addr, rc); +				cifs_dbg(VFS, "Failed to bind to: %pI6c, error: %d\n", +					 &saddr6->sin6_addr, rc);  			else -				cERROR(1, "cifs: " -				       "Failed to bind to: %pI4, error: %d\n", -				       &saddr4->sin_addr.s_addr, rc); +				cifs_dbg(VFS, "Failed to bind to: %pI4, error: %d\n", +					 &saddr4->sin_addr.s_addr, rc);  		}  	}  	return rc;  }  static int -ipv4_connect(struct TCP_Server_Info *server) +ip_rfc1001_connect(struct TCP_Server_Info *server)  {  	int rc = 0; -	int val; -	bool connected = false; -	__be16 orig_port = 0; +	/* +	 * some servers require RFC1001 sessinit before sending +	 * negprot - BB check reconnection in case where second +	 * sessinit is sent but no second negprot +	 */ +	struct rfc1002_session_packet *ses_init_buf; +	struct smb_hdr *smb_buf; +	ses_init_buf = kzalloc(sizeof(struct rfc1002_session_packet), +			       GFP_KERNEL); +	if (ses_init_buf) { +		ses_init_buf->trailer.session_req.called_len = 32; + +		if (server->server_RFC1001_name && +		    server->server_RFC1001_name[0] != 0) +			rfc1002mangle(ses_init_buf->trailer. +				      session_req.called_name, +				      server->server_RFC1001_name, +				      RFC1001_NAME_LEN_WITH_NULL); +		else +			rfc1002mangle(ses_init_buf->trailer. +				      session_req.called_name, +				      DEFAULT_CIFS_CALLED_NAME, +				      RFC1001_NAME_LEN_WITH_NULL); + +		ses_init_buf->trailer.session_req.calling_len = 32; + +		/* +		 * calling name ends in null (byte 16) from old smb +		 * convention. +		 */ +		if (server->workstation_RFC1001_name && +		    server->workstation_RFC1001_name[0] != 0) +			rfc1002mangle(ses_init_buf->trailer. +				      session_req.calling_name, +				      server->workstation_RFC1001_name, +				      RFC1001_NAME_LEN_WITH_NULL); +		else +			rfc1002mangle(ses_init_buf->trailer. +				      session_req.calling_name, +				      "LINUX_CIFS_CLNT", +				      RFC1001_NAME_LEN_WITH_NULL); + +		ses_init_buf->trailer.session_req.scope1 = 0; +		ses_init_buf->trailer.session_req.scope2 = 0; +		smb_buf = (struct smb_hdr *)ses_init_buf; + +		/* sizeof RFC1002_SESSION_REQUEST with no scope */ +		smb_buf->smb_buf_length = cpu_to_be32(0x81000044); +		rc = smb_send(server, smb_buf, 0x44); +		kfree(ses_init_buf); +		/* +		 * RFC1001 layer in at least one server +		 * requires very short break before negprot +		 * presumably because not expecting negprot +		 * to follow so fast.  This is a simple +		 * solution that works without +		 * complicating the code and causes no +		 * significant slowing down on mount +		 * for everyone else +		 */ +		usleep_range(1000, 2000); +	} +	/* +	 * else the negprot may still work without this +	 * even though malloc failed +	 */ + +	return rc; +} + +static int +generic_ip_connect(struct TCP_Server_Info *server) +{ +	int rc = 0; +	__be16 sport; +	int slen, sfamily;  	struct socket *socket = server->ssocket; +	struct sockaddr *saddr; + +	saddr = (struct sockaddr *) &server->dstaddr; + +	if (server->dstaddr.ss_family == AF_INET6) { +		sport = ((struct sockaddr_in6 *) saddr)->sin6_port; +		slen = sizeof(struct sockaddr_in6); +		sfamily = AF_INET6; +	} else { +		sport = ((struct sockaddr_in *) saddr)->sin_port; +		slen = sizeof(struct sockaddr_in); +		sfamily = AF_INET; +	}  	if (socket == NULL) { -		rc = sock_create_kern(PF_INET, SOCK_STREAM, -				      IPPROTO_TCP, &socket); +		rc = __sock_create(cifs_net_ns(server), sfamily, SOCK_STREAM, +				   IPPROTO_TCP, &socket, 1);  		if (rc < 0) { -			cERROR(1, "Error %d creating socket", rc); +			cifs_dbg(VFS, "Error %d creating socket\n", rc); +			server->ssocket = NULL;  			return rc;  		}  		/* BB other socket options to set KEEPALIVE, NODELAY? */ -		cFYI(1, "Socket created"); +		cifs_dbg(FYI, "Socket created\n");  		server->ssocket = socket;  		socket->sk->sk_allocation = GFP_NOFS; -		cifs_reclassify_socket4(socket); +		if (sfamily == AF_INET6) +			cifs_reclassify_socket6(socket); +		else +			cifs_reclassify_socket4(socket);  	}  	rc = bind_socket(server);  	if (rc < 0)  		return rc; -	/* user overrode default port */ -	if (server->addr.sockAddr.sin_port) { -		rc = socket->ops->connect(socket, (struct sockaddr *) -					  &server->addr.sockAddr, -					  sizeof(struct sockaddr_in), 0); -		if (rc >= 0) -			connected = true; -	} - -	if (!connected) { -		/* save original port so we can retry user specified port -			later if fall back ports fail this time  */ -		orig_port = server->addr.sockAddr.sin_port; - -		/* do not retry on the same port we just failed on */ -		if (server->addr.sockAddr.sin_port != htons(CIFS_PORT)) { -			server->addr.sockAddr.sin_port = htons(CIFS_PORT); -			rc = socket->ops->connect(socket, -						(struct sockaddr *) -						&server->addr.sockAddr, -						sizeof(struct sockaddr_in), 0); -			if (rc >= 0) -				connected = true; -		} -	} -	if (!connected) { -		server->addr.sockAddr.sin_port = htons(RFC1001_PORT); -		rc = socket->ops->connect(socket, (struct sockaddr *) -					      &server->addr.sockAddr, -					      sizeof(struct sockaddr_in), 0); -		if (rc >= 0) -			connected = true; -	} - -	/* give up here - unless we want to retry on different -		protocol families some day */ -	if (!connected) { -		if (orig_port) -			server->addr.sockAddr.sin_port = orig_port; -		cFYI(1, "Error %d connecting to server via ipv4", rc); -		sock_release(socket); -		server->ssocket = NULL; -		return rc; -	} - -  	/*  	 * Eventually check for other socket options to change from -	 *  the default. sock_setsockopt not used because it expects -	 *  user space buffer +	 * the default. sock_setsockopt not used because it expects +	 * user space buffer  	 */  	socket->sk->sk_rcvtimeo = 7 * HZ;  	socket->sk->sk_sndtimeo = 5 * HZ; @@ -2211,176 +3008,63 @@ ipv4_connect(struct TCP_Server_Info *server)  	}  	if (server->tcp_nodelay) { -		val = 1; +		int val = 1;  		rc = kernel_setsockopt(socket, SOL_TCP, TCP_NODELAY,  				(char *)&val, sizeof(val));  		if (rc) -			cFYI(1, "set TCP_NODELAY socket option error %d", rc); +			cifs_dbg(FYI, "set TCP_NODELAY socket option error %d\n", +				 rc);  	} -	 cFYI(1, "sndbuf %d rcvbuf %d rcvtimeo 0x%lx", +	cifs_dbg(FYI, "sndbuf %d rcvbuf %d rcvtimeo 0x%lx\n",  		 socket->sk->sk_sndbuf,  		 socket->sk->sk_rcvbuf, socket->sk->sk_rcvtimeo); -	/* send RFC1001 sessinit */ -	if (server->addr.sockAddr.sin_port == htons(RFC1001_PORT)) { -		/* some servers require RFC1001 sessinit before sending -		negprot - BB check reconnection in case where second -		sessinit is sent but no second negprot */ -		struct rfc1002_session_packet *ses_init_buf; -		struct smb_hdr *smb_buf; -		ses_init_buf = kzalloc(sizeof(struct rfc1002_session_packet), -				       GFP_KERNEL); -		if (ses_init_buf) { -			ses_init_buf->trailer.session_req.called_len = 32; -			if (server->server_RFC1001_name && -			    server->server_RFC1001_name[0] != 0) -				rfc1002mangle(ses_init_buf->trailer. -						session_req.called_name, -					      server->server_RFC1001_name, -					      RFC1001_NAME_LEN_WITH_NULL); -			else -				rfc1002mangle(ses_init_buf->trailer. -						session_req.called_name, -					      DEFAULT_CIFS_CALLED_NAME, -					      RFC1001_NAME_LEN_WITH_NULL); - -			ses_init_buf->trailer.session_req.calling_len = 32; - -			/* calling name ends in null (byte 16) from old smb -			convention. */ -			if (server->workstation_RFC1001_name && -			    server->workstation_RFC1001_name[0] != 0) -				rfc1002mangle(ses_init_buf->trailer. -						session_req.calling_name, -					      server->workstation_RFC1001_name, -					      RFC1001_NAME_LEN_WITH_NULL); -			else -				rfc1002mangle(ses_init_buf->trailer. -						session_req.calling_name, -					      "LINUX_CIFS_CLNT", -					      RFC1001_NAME_LEN_WITH_NULL); - -			ses_init_buf->trailer.session_req.scope1 = 0; -			ses_init_buf->trailer.session_req.scope2 = 0; -			smb_buf = (struct smb_hdr *)ses_init_buf; -			/* sizeof RFC1002_SESSION_REQUEST with no scope */ -			smb_buf->smb_buf_length = 0x81000044; -			rc = smb_send(server, smb_buf, 0x44); -			kfree(ses_init_buf); -			msleep(1); /* RFC1001 layer in at least one server -				      requires very short break before negprot -				      presumably because not expecting negprot -				      to follow so fast.  This is a simple -				      solution that works without -				      complicating the code and causes no -				      significant slowing down on mount -				      for everyone else */ -		} -		/* else the negprot may still work without this -		even though malloc failed */ - +	rc = socket->ops->connect(socket, saddr, slen, 0); +	if (rc < 0) { +		cifs_dbg(FYI, "Error %d connecting to server\n", rc); +		sock_release(socket); +		server->ssocket = NULL; +		return rc;  	} +	if (sport == htons(RFC1001_PORT)) +		rc = ip_rfc1001_connect(server); +  	return rc;  }  static int -ipv6_connect(struct TCP_Server_Info *server) +ip_connect(struct TCP_Server_Info *server)  { -	int rc = 0; -	int val; -	bool connected = false; -	__be16 orig_port = 0; -	struct socket *socket = server->ssocket; +	__be16 *sport; +	struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&server->dstaddr; +	struct sockaddr_in *addr = (struct sockaddr_in *)&server->dstaddr; -	if (socket == NULL) { -		rc = sock_create_kern(PF_INET6, SOCK_STREAM, -				      IPPROTO_TCP, &socket); -		if (rc < 0) { -			cERROR(1, "Error %d creating ipv6 socket", rc); -			socket = NULL; -			return rc; -		} +	if (server->dstaddr.ss_family == AF_INET6) +		sport = &addr6->sin6_port; +	else +		sport = &addr->sin_port; -		/* BB other socket options to set KEEPALIVE, NODELAY? */ -		cFYI(1, "ipv6 Socket created"); -		server->ssocket = socket; -		socket->sk->sk_allocation = GFP_NOFS; -		cifs_reclassify_socket6(socket); -	} +	if (*sport == 0) { +		int rc; -	rc = bind_socket(server); -	if (rc < 0) -		return rc; +		/* try with 445 port at first */ +		*sport = htons(CIFS_PORT); -	/* user overrode default port */ -	if (server->addr.sockAddr6.sin6_port) { -		rc = socket->ops->connect(socket, -				(struct sockaddr *) &server->addr.sockAddr6, -				sizeof(struct sockaddr_in6), 0); +		rc = generic_ip_connect(server);  		if (rc >= 0) -			connected = true; -	} - -	if (!connected) { -		/* save original port so we can retry user specified port -			later if fall back ports fail this time  */ - -		orig_port = server->addr.sockAddr6.sin6_port; -		/* do not retry on the same port we just failed on */ -		if (server->addr.sockAddr6.sin6_port != htons(CIFS_PORT)) { -			server->addr.sockAddr6.sin6_port = htons(CIFS_PORT); -			rc = socket->ops->connect(socket, (struct sockaddr *) -					&server->addr.sockAddr6, -					sizeof(struct sockaddr_in6), 0); -			if (rc >= 0) -				connected = true; -		} -	} -	if (!connected) { -		server->addr.sockAddr6.sin6_port = htons(RFC1001_PORT); -		rc = socket->ops->connect(socket, (struct sockaddr *) -				&server->addr.sockAddr6, -				sizeof(struct sockaddr_in6), 0); -		if (rc >= 0) -			connected = true; -	} - -	/* give up here - unless we want to retry on different -		protocol families some day */ -	if (!connected) { -		if (orig_port) -			server->addr.sockAddr6.sin6_port = orig_port; -		cFYI(1, "Error %d connecting to server via ipv6", rc); -		sock_release(socket); -		server->ssocket = NULL; -		return rc; -	} - -	/* -	 * Eventually check for other socket options to change from -	 * the default. sock_setsockopt not used because it expects -	 * user space buffer -	 */ -	socket->sk->sk_rcvtimeo = 7 * HZ; -	socket->sk->sk_sndtimeo = 5 * HZ; +			return rc; -	if (server->tcp_nodelay) { -		val = 1; -		rc = kernel_setsockopt(socket, SOL_TCP, TCP_NODELAY, -				(char *)&val, sizeof(val)); -		if (rc) -			cFYI(1, "set TCP_NODELAY socket option error %d", rc); +		/* if it failed, try with 139 port */ +		*sport = htons(RFC1001_PORT);  	} -	server->ssocket = socket; - -	return rc; +	return generic_ip_connect(server);  } -void reset_cifs_unix_caps(int xid, struct cifsTconInfo *tcon, -			  struct super_block *sb, struct smb_vol *vol_info) +void reset_cifs_unix_caps(unsigned int xid, struct cifs_tcon *tcon, +			  struct cifs_sb_info *cifs_sb, struct smb_vol *vol_info)  {  	/* if we are reconnecting then should we check to see if  	 * any requested capabilities changed locally e.g. via @@ -2396,19 +3080,19 @@ void reset_cifs_unix_caps(int xid, struct cifsTconInfo *tcon,  	if (vol_info && vol_info->no_linux_ext) {  		tcon->fsUnixInfo.Capability = 0;  		tcon->unix_ext = 0; /* Unix Extensions disabled */ -		cFYI(1, "Linux protocol extensions disabled"); +		cifs_dbg(FYI, "Linux protocol extensions disabled\n");  		return;  	} else if (vol_info)  		tcon->unix_ext = 1; /* Unix Extensions supported */  	if (tcon->unix_ext == 0) { -		cFYI(1, "Unix extensions disabled so not set on reconnect"); +		cifs_dbg(FYI, "Unix extensions disabled so not set on reconnect\n");  		return;  	}  	if (!CIFSSMBQFSUnixInfo(xid, tcon)) {  		__u64 cap = le64_to_cpu(tcon->fsUnixInfo.Capability); - +		cifs_dbg(FYI, "unix caps which server supports %lld\n", cap);  		/* check for reconnect case in which we do not  		   want to change the mount behavior if we can avoid it */  		if (vol_info == NULL) { @@ -2418,153 +3102,91 @@ void reset_cifs_unix_caps(int xid, struct cifsTconInfo *tcon,  				cap &= ~CIFS_UNIX_POSIX_ACL_CAP;  			if ((saved_cap & CIFS_UNIX_POSIX_PATHNAMES_CAP) == 0) {  				if (cap & CIFS_UNIX_POSIX_PATHNAMES_CAP) -					cERROR(1, "POSIXPATH support change"); +					cifs_dbg(VFS, "POSIXPATH support change\n");  				cap &= ~CIFS_UNIX_POSIX_PATHNAMES_CAP;  			} else if ((cap & CIFS_UNIX_POSIX_PATHNAMES_CAP) == 0) { -				cERROR(1, "possible reconnect error"); -				cERROR(1, "server disabled POSIX path support"); +				cifs_dbg(VFS, "possible reconnect error\n"); +				cifs_dbg(VFS, "server disabled POSIX path support\n");  			}  		} +		if (cap & CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP) +			cifs_dbg(VFS, "per-share encryption not supported yet\n"); +  		cap &= CIFS_UNIX_CAP_MASK;  		if (vol_info && vol_info->no_psx_acl)  			cap &= ~CIFS_UNIX_POSIX_ACL_CAP;  		else if (CIFS_UNIX_POSIX_ACL_CAP & cap) { -			cFYI(1, "negotiated posix acl support"); -			if (sb) -				sb->s_flags |= MS_POSIXACL; +			cifs_dbg(FYI, "negotiated posix acl support\n"); +			if (cifs_sb) +				cifs_sb->mnt_cifs_flags |= +					CIFS_MOUNT_POSIXACL;  		}  		if (vol_info && vol_info->posix_paths == 0)  			cap &= ~CIFS_UNIX_POSIX_PATHNAMES_CAP;  		else if (cap & CIFS_UNIX_POSIX_PATHNAMES_CAP) { -			cFYI(1, "negotiate posix pathnames"); -			if (sb) -				CIFS_SB(sb)->mnt_cifs_flags |= +			cifs_dbg(FYI, "negotiate posix pathnames\n"); +			if (cifs_sb) +				cifs_sb->mnt_cifs_flags |=  					CIFS_MOUNT_POSIX_PATHS;  		} -		/* We might be setting the path sep back to a different -		form if we are reconnecting and the server switched its -		posix path capability for this share */ -		if (sb && (CIFS_SB(sb)->prepathlen > 0)) -			CIFS_SB(sb)->prepath[0] = CIFS_DIR_SEP(CIFS_SB(sb)); - -		if (sb && (CIFS_SB(sb)->rsize > 127 * 1024)) { -			if ((cap & CIFS_UNIX_LARGE_READ_CAP) == 0) { -				CIFS_SB(sb)->rsize = 127 * 1024; -				cFYI(DBG2, "larger reads not supported by srv"); -			} -		} - - -		cFYI(1, "Negotiate caps 0x%x", (int)cap); +		cifs_dbg(FYI, "Negotiate caps 0x%x\n", (int)cap);  #ifdef CONFIG_CIFS_DEBUG2  		if (cap & CIFS_UNIX_FCNTL_CAP) -			cFYI(1, "FCNTL cap"); +			cifs_dbg(FYI, "FCNTL cap\n");  		if (cap & CIFS_UNIX_EXTATTR_CAP) -			cFYI(1, "EXTATTR cap"); +			cifs_dbg(FYI, "EXTATTR cap\n");  		if (cap & CIFS_UNIX_POSIX_PATHNAMES_CAP) -			cFYI(1, "POSIX path cap"); +			cifs_dbg(FYI, "POSIX path cap\n");  		if (cap & CIFS_UNIX_XATTR_CAP) -			cFYI(1, "XATTR cap"); +			cifs_dbg(FYI, "XATTR cap\n");  		if (cap & CIFS_UNIX_POSIX_ACL_CAP) -			cFYI(1, "POSIX ACL cap"); +			cifs_dbg(FYI, "POSIX ACL cap\n");  		if (cap & CIFS_UNIX_LARGE_READ_CAP) -			cFYI(1, "very large read cap"); +			cifs_dbg(FYI, "very large read cap\n");  		if (cap & CIFS_UNIX_LARGE_WRITE_CAP) -			cFYI(1, "very large write cap"); +			cifs_dbg(FYI, "very large write cap\n"); +		if (cap & CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP) +			cifs_dbg(FYI, "transport encryption cap\n"); +		if (cap & CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP) +			cifs_dbg(FYI, "mandatory transport encryption cap\n");  #endif /* CIFS_DEBUG2 */  		if (CIFSSMBSetFSUnixInfo(xid, tcon, cap)) {  			if (vol_info == NULL) { -				cFYI(1, "resetting capabilities failed"); +				cifs_dbg(FYI, "resetting capabilities failed\n");  			} else -				cERROR(1, "Negotiating Unix capabilities " -					   "with the server failed.  Consider " -					   "mounting with the Unix Extensions\n" -					   "disabled, if problems are found, " -					   "by specifying the nounix mount " -					   "option."); +				cifs_dbg(VFS, "Negotiating Unix capabilities with the server failed. Consider mounting with the Unix Extensions disabled if problems are found by specifying the nounix mount option.\n");  		}  	}  } -static void -convert_delimiter(char *path, char delim) +void cifs_setup_cifs_sb(struct smb_vol *pvolume_info, +			struct cifs_sb_info *cifs_sb)  { -	int i; -	char old_delim; - -	if (path == NULL) -		return; +	INIT_DELAYED_WORK(&cifs_sb->prune_tlinks, cifs_prune_tlinks); -	if (delim == '/') -		old_delim = '\\'; -	else -		old_delim = '/'; +	spin_lock_init(&cifs_sb->tlink_tree_lock); +	cifs_sb->tlink_tree = RB_ROOT; -	for (i = 0; path[i] != '\0'; i++) { -		if (path[i] == old_delim) -			path[i] = delim; -	} -} - -static void setup_cifs_sb(struct smb_vol *pvolume_info, -			  struct cifs_sb_info *cifs_sb) -{ -	INIT_DELAYED_WORK(&cifs_sb->prune_tlinks, cifs_prune_tlinks); +	/* +	 * Temporarily set r/wsize for matching superblock. If we end up using +	 * new sb then client will later negotiate it downward if needed. +	 */ +	cifs_sb->rsize = pvolume_info->rsize; +	cifs_sb->wsize = pvolume_info->wsize; -	if (pvolume_info->rsize > CIFSMaxBufSize) { -		cERROR(1, "rsize %d too large, using MaxBufSize", -			pvolume_info->rsize); -		cifs_sb->rsize = CIFSMaxBufSize; -	} else if ((pvolume_info->rsize) && -			(pvolume_info->rsize <= CIFSMaxBufSize)) -		cifs_sb->rsize = pvolume_info->rsize; -	else /* default */ -		cifs_sb->rsize = CIFSMaxBufSize; - -	if (pvolume_info->wsize > PAGEVEC_SIZE * PAGE_CACHE_SIZE) { -		cERROR(1, "wsize %d too large, using 4096 instead", -			  pvolume_info->wsize); -		cifs_sb->wsize = 4096; -	} else if (pvolume_info->wsize) -		cifs_sb->wsize = pvolume_info->wsize; -	else -		cifs_sb->wsize = min_t(const int, -					PAGEVEC_SIZE * PAGE_CACHE_SIZE, -					127*1024); -		/* old default of CIFSMaxBufSize was too small now -		   that SMB Write2 can send multiple pages in kvec. -		   RFC1001 does not describe what happens when frame -		   bigger than 128K is sent so use that as max in -		   conjunction with 52K kvec constraint on arch with 4K -		   page size  */ - -	if (cifs_sb->rsize < 2048) { -		cifs_sb->rsize = 2048; -		/* Windows ME may prefer this */ -		cFYI(1, "readsize set to minimum: 2048"); -	} -	/* calculate prepath */ -	cifs_sb->prepath = pvolume_info->prepath; -	if (cifs_sb->prepath) { -		cifs_sb->prepathlen = strlen(cifs_sb->prepath); -		/* we can not convert the / to \ in the path -		separators in the prefixpath yet because we do not -		know (until reset_cifs_unix_caps is called later) -		whether POSIX PATH CAP is available. We normalize -		the / to \ after reset_cifs_unix_caps is called */ -		pvolume_info->prepath = NULL; -	} else -		cifs_sb->prepathlen = 0;  	cifs_sb->mnt_uid = pvolume_info->linux_uid;  	cifs_sb->mnt_gid = pvolume_info->linux_gid;  	cifs_sb->mnt_file_mode = pvolume_info->file_mode;  	cifs_sb->mnt_dir_mode = pvolume_info->dir_mode; -	cFYI(1, "file mode: 0x%x  dir mode: 0x%x", -		cifs_sb->mnt_file_mode, cifs_sb->mnt_dir_mode); +	cifs_dbg(FYI, "file mode: 0x%hx  dir mode: 0x%hx\n", +		 cifs_sb->mnt_file_mode, cifs_sb->mnt_dir_mode); + +	cifs_sb->actimeo = pvolume_info->actimeo; +	cifs_sb->local_nls = pvolume_info->local_nls;  	if (pvolume_info->noperm)  		cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NO_PERM; @@ -2584,8 +3206,18 @@ static void setup_cifs_sb(struct smb_vol *pvolume_info,  		cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NOSSYNC;  	if (pvolume_info->mand_lock)  		cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NOPOSIXBRL; +	if (pvolume_info->rwpidforward) +		cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_RWPIDFORWARD;  	if (pvolume_info->cifs_acl)  		cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_CIFS_ACL; +	if (pvolume_info->backupuid_specified) { +		cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_CIFS_BACKUPUID; +		cifs_sb->mnt_backupuid = pvolume_info->backupuid; +	} +	if (pvolume_info->backupgid_specified) { +		cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_CIFS_BACKUPGID; +		cifs_sb->mnt_backupgid = pvolume_info->backupgid; +	}  	if (pvolume_info->override_uid)  		cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_OVERR_UID;  	if (pvolume_info->override_gid) @@ -2597,143 +3229,155 @@ static void setup_cifs_sb(struct smb_vol *pvolume_info,  	if (pvolume_info->multiuser)  		cifs_sb->mnt_cifs_flags |= (CIFS_MOUNT_MULTIUSER |  					    CIFS_MOUNT_NO_PERM); +	if (pvolume_info->strict_io) +		cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_STRICT_IO;  	if (pvolume_info->direct_io) { -		cFYI(1, "mounting share using direct i/o"); +		cifs_dbg(FYI, "mounting share using direct i/o\n");  		cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DIRECT_IO;  	}  	if (pvolume_info->mfsymlinks) {  		if (pvolume_info->sfu_emul) { -			cERROR(1,  "mount option mfsymlinks ignored if sfu " -				   "mount option is used"); +			cifs_dbg(VFS, "mount option mfsymlinks ignored if sfu mount option is used\n");  		} else {  			cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_MF_SYMLINKS;  		}  	}  	if ((pvolume_info->cifs_acl) && (pvolume_info->dynperm)) -		cERROR(1, "mount option dynperm ignored if cifsacl " -			   "mount option supported"); -} - -static int -is_path_accessible(int xid, struct cifsTconInfo *tcon, -		   struct cifs_sb_info *cifs_sb, const char *full_path) -{ -	int rc; -	FILE_ALL_INFO *pfile_info; - -	pfile_info = kmalloc(sizeof(FILE_ALL_INFO), GFP_KERNEL); -	if (pfile_info == NULL) -		return -ENOMEM; - -	rc = CIFSSMBQPathInfo(xid, tcon, full_path, pfile_info, -			      0 /* not legacy */, cifs_sb->local_nls, -			      cifs_sb->mnt_cifs_flags & -				CIFS_MOUNT_MAP_SPECIAL_CHR); -	kfree(pfile_info); -	return rc; +		cifs_dbg(VFS, "mount option dynperm ignored if cifsacl mount option supported\n");  }  static void -cleanup_volume_info(struct smb_vol **pvolume_info) +cleanup_volume_info_contents(struct smb_vol *volume_info)  { -	struct smb_vol *volume_info; - -	if (!pvolume_info || !*pvolume_info) -		return; - -	volume_info = *pvolume_info; +	kfree(volume_info->username);  	kzfree(volume_info->password);  	kfree(volume_info->UNC); +	kfree(volume_info->domainname); +	kfree(volume_info->iocharset);  	kfree(volume_info->prepath); +} + +void +cifs_cleanup_volume_info(struct smb_vol *volume_info) +{ +	if (!volume_info) +		return; +	cleanup_volume_info_contents(volume_info);  	kfree(volume_info); -	*pvolume_info = NULL; -	return;  } +  #ifdef CONFIG_CIFS_DFS_UPCALL -/* build_path_to_root returns full path to root when - * we do not have an exiting connection (tcon) */ +/* + * cifs_build_path_to_root returns full path to root when we do not have an + * exiting connection (tcon) + */  static char * -build_unc_path_to_root(const struct smb_vol *volume_info, +build_unc_path_to_root(const struct smb_vol *vol,  		const struct cifs_sb_info *cifs_sb)  { -	char *full_path; +	char *full_path, *pos; +	unsigned int pplen = vol->prepath ? strlen(vol->prepath) + 1 : 0; +	unsigned int unc_len = strnlen(vol->UNC, MAX_TREE_SIZE + 1); -	int unc_len = strnlen(volume_info->UNC, MAX_TREE_SIZE + 1); -	full_path = kmalloc(unc_len + cifs_sb->prepathlen + 1, GFP_KERNEL); +	full_path = kmalloc(unc_len + pplen + 1, GFP_KERNEL);  	if (full_path == NULL)  		return ERR_PTR(-ENOMEM); -	strncpy(full_path, volume_info->UNC, unc_len); -	if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) { -		int i; -		for (i = 0; i < unc_len; i++) { -			if (full_path[i] == '\\') -				full_path[i] = '/'; -		} -	} +	strncpy(full_path, vol->UNC, unc_len); +	pos = full_path + unc_len; -	if (cifs_sb->prepathlen) -		strncpy(full_path + unc_len, cifs_sb->prepath, -				cifs_sb->prepathlen); +	if (pplen) { +		*pos = CIFS_DIR_SEP(cifs_sb); +		strncpy(pos + 1, vol->prepath, pplen); +		pos += pplen; +	} -	full_path[unc_len + cifs_sb->prepathlen] = 0; /* add trailing null */ +	*pos = '\0'; /* add trailing null */ +	convert_delimiter(full_path, CIFS_DIR_SEP(cifs_sb)); +	cifs_dbg(FYI, "%s: full_path=%s\n", __func__, full_path);  	return full_path;  } -#endif -int -cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb, -		char *mount_data_global, const char *devname) +/* + * Perform a dfs referral query for a share and (optionally) prefix + * + * If a referral is found, cifs_sb->mountdata will be (re-)allocated + * to a string containing updated options for the submount.  Otherwise it + * will be left untouched. + * + * Returns the rc from get_dfs_path to the caller, which can be used to + * determine whether there were referrals. + */ +static int +expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses, +		    struct smb_vol *volume_info, struct cifs_sb_info *cifs_sb, +		    int check_prefix)  {  	int rc; -	int xid; -	struct smb_vol *volume_info; -	struct cifsSesInfo *pSesInfo; -	struct cifsTconInfo *tcon; -	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; -	int referral_walks_count = 0; -try_mount_again: -#endif -	rc = 0; -	tcon = NULL; -	pSesInfo = NULL; -	srvTcp = NULL; -	full_path = NULL; -	tlink = NULL; +	struct dfs_info3_param *referrals = NULL; +	char *full_path = NULL, *ref_path = NULL, *mdata = NULL; -	xid = GetXid(); +	full_path = build_unc_path_to_root(volume_info, cifs_sb); +	if (IS_ERR(full_path)) +		return PTR_ERR(full_path); -	volume_info = kzalloc(sizeof(struct smb_vol), GFP_KERNEL); -	if (!volume_info) { -		rc = -ENOMEM; -		goto out; -	} +	/* For DFS paths, skip the first '\' of the UNC */ +	ref_path = check_prefix ? full_path + 1 : volume_info->UNC + 1; -	if (cifs_parse_mount_options(mount_data, devname, volume_info)) { -		rc = -EINVAL; -		goto out; +	rc = get_dfs_path(xid, ses, ref_path, cifs_sb->local_nls, +			  &num_referrals, &referrals, +			  cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR); + +	if (!rc && num_referrals > 0) { +		char *fake_devname = NULL; + +		mdata = cifs_compose_mount_options(cifs_sb->mountdata, +						   full_path + 1, referrals, +						   &fake_devname); + +		free_dfs_info_array(referrals, num_referrals); + +		if (IS_ERR(mdata)) { +			rc = PTR_ERR(mdata); +			mdata = NULL; +		} else { +			cleanup_volume_info_contents(volume_info); +			rc = cifs_setup_volume_info(volume_info, mdata, +							fake_devname); +		} +		kfree(fake_devname); +		kfree(cifs_sb->mountdata); +		cifs_sb->mountdata = mdata;  	} +	kfree(full_path); +	return rc; +} +#endif + +static int +cifs_setup_volume_info(struct smb_vol *volume_info, char *mount_data, +			const char *devname) +{ +	int rc = 0; + +	if (cifs_parse_mount_options(mount_data, devname, volume_info)) +		return -EINVAL;  	if (volume_info->nullauth) { -		cFYI(1, "null user"); -		volume_info->username = ""; +		cifs_dbg(FYI, "Anonymous login\n"); +		kfree(volume_info->username); +		volume_info->username = NULL;  	} else if (volume_info->username) {  		/* BB fixme parse for domain name here */ -		cFYI(1, "Username: %s", volume_info->username); +		cifs_dbg(FYI, "Username: %s\n", volume_info->username);  	} else { -		cifserror("No username specified"); +		cifs_dbg(VFS, "No username specified\n");  	/* In userspace mount helper we can get user name from alternate  	   locations such as env variables and files on disk */ -		rc = -EINVAL; -		goto out; +		return -EINVAL;  	}  	/* this is needed for ASCII cp to Unicode converts */ @@ -2743,86 +3387,157 @@ try_mount_again:  	} else {  		volume_info->local_nls = load_nls(volume_info->iocharset);  		if (volume_info->local_nls == NULL) { -			cERROR(1, "CIFS mount error: iocharset %s not found", +			cifs_dbg(VFS, "CIFS mount error: iocharset %s not found\n",  				 volume_info->iocharset); -			rc = -ELIBACC; -			goto out; +			return -ELIBACC;  		}  	} -	cifs_sb->local_nls = volume_info->local_nls; + +	return rc; +} + +struct smb_vol * +cifs_get_volume_info(char *mount_data, const char *devname) +{ +	int rc; +	struct smb_vol *volume_info; + +	volume_info = kmalloc(sizeof(struct smb_vol), GFP_KERNEL); +	if (!volume_info) +		return ERR_PTR(-ENOMEM); + +	rc = cifs_setup_volume_info(volume_info, mount_data, devname); +	if (rc) { +		cifs_cleanup_volume_info(volume_info); +		volume_info = ERR_PTR(rc); +	} + +	return volume_info; +} + +int +cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info) +{ +	int rc; +	unsigned int xid; +	struct cifs_ses *ses; +	struct cifs_tcon *tcon; +	struct TCP_Server_Info *server; +	char   *full_path; +	struct tcon_link *tlink; +#ifdef CONFIG_CIFS_DFS_UPCALL +	int referral_walks_count = 0; +#endif + +	rc = bdi_setup_and_register(&cifs_sb->bdi, "cifs", BDI_CAP_MAP_COPY); +	if (rc) +		return rc; + +#ifdef CONFIG_CIFS_DFS_UPCALL +try_mount_again: +	/* cleanup activities if we're chasing a referral */ +	if (referral_walks_count) { +		if (tcon) +			cifs_put_tcon(tcon); +		else if (ses) +			cifs_put_smb_ses(ses); + +		free_xid(xid); +	} +#endif +	rc = 0; +	tcon = NULL; +	ses = NULL; +	server = NULL; +	full_path = NULL; +	tlink = NULL; + +	xid = get_xid();  	/* get a reference to a tcp session */ -	srvTcp = cifs_get_tcp_session(volume_info); -	if (IS_ERR(srvTcp)) { -		rc = PTR_ERR(srvTcp); +	server = cifs_get_tcp_session(volume_info); +	if (IS_ERR(server)) { +		rc = PTR_ERR(server); +		bdi_destroy(&cifs_sb->bdi);  		goto out;  	}  	/* get a reference to a SMB session */ -	pSesInfo = cifs_get_smb_ses(srvTcp, volume_info); -	if (IS_ERR(pSesInfo)) { -		rc = PTR_ERR(pSesInfo); -		pSesInfo = NULL; +	ses = cifs_get_smb_ses(server, volume_info); +	if (IS_ERR(ses)) { +		rc = PTR_ERR(ses); +		ses = NULL;  		goto mount_fail_check;  	} -	setup_cifs_sb(volume_info, cifs_sb); -	if (pSesInfo->capabilities & CAP_LARGE_FILES) -		sb->s_maxbytes = MAX_LFS_FILESIZE; -	else -		sb->s_maxbytes = MAX_NON_LFS; - -	/* BB FIXME fix time_gran to be larger for LANMAN sessions */ -	sb->s_time_gran = 100; -  	/* search for existing tcon to this server share */ -	tcon = cifs_get_tcon(pSesInfo, volume_info); +	tcon = cifs_get_tcon(ses, volume_info);  	if (IS_ERR(tcon)) {  		rc = PTR_ERR(tcon);  		tcon = NULL;  		goto remote_path_check;  	} -	/* do not care if following two calls succeed - informational */ -	if (!tcon->ipc) { -		CIFSSMBQFSDeviceInfo(xid, tcon); -		CIFSSMBQFSAttributeInfo(xid, tcon); -	} -  	/* tell server which Unix caps we support */ -	if (tcon->ses->capabilities & CAP_UNIX) +	if (cap_unix(tcon->ses)) {  		/* reset of caps checks mount to see if unix extensions  		   disabled for just this mount */ -		reset_cifs_unix_caps(xid, tcon, sb, volume_info); -	else +		reset_cifs_unix_caps(xid, tcon, cifs_sb, volume_info); +		if ((tcon->ses->server->tcpStatus == CifsNeedReconnect) && +		    (le64_to_cpu(tcon->fsUnixInfo.Capability) & +		     CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP)) { +			rc = -EACCES; +			goto mount_fail_check; +		} +	} else  		tcon->unix_ext = 0; /* server does not support them */ -	/* convert forward to back slashes in prepath here if needed */ -	if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) == 0) -		convert_delimiter(cifs_sb->prepath, CIFS_DIR_SEP(cifs_sb)); +	/* do not care if a following call succeed - informational */ +	if (!tcon->ipc && server->ops->qfs_tcon) +		server->ops->qfs_tcon(xid, tcon); -	if ((tcon->unix_ext == 0) && (cifs_sb->rsize > (1024 * 127))) { -		cifs_sb->rsize = 1024 * 127; -		cFYI(DBG2, "no very large read support, rsize now 127K"); -	} -	if (!(tcon->ses->capabilities & CAP_LARGE_WRITE_X)) -		cifs_sb->wsize = min(cifs_sb->wsize, -			       (tcon->ses->server->maxBuf - MAX_CIFS_HDR_SIZE)); -	if (!(tcon->ses->capabilities & CAP_LARGE_READ_X)) -		cifs_sb->rsize = min(cifs_sb->rsize, -			       (tcon->ses->server->maxBuf - MAX_CIFS_HDR_SIZE)); +	cifs_sb->wsize = server->ops->negotiate_wsize(tcon, volume_info); +	cifs_sb->rsize = server->ops->negotiate_rsize(tcon, volume_info); + +	/* tune readahead according to rsize */ +	cifs_sb->bdi.ra_pages = cifs_sb->rsize / PAGE_CACHE_SIZE;  remote_path_check: -	/* check if a whole path (including prepath) is not remote */ -	if (!rc && cifs_sb->prepathlen && tcon) { -		/* build_path_to_root works only when we have a valid tcon */ -		full_path = cifs_build_path_to_root(cifs_sb); +#ifdef CONFIG_CIFS_DFS_UPCALL +	/* +	 * Perform an unconditional check for whether there are DFS +	 * referrals for this path without prefix, to provide support +	 * for DFS referrals from w2k8 servers which don't seem to respond +	 * with PATH_NOT_COVERED to requests that include the prefix. +	 * Chase the referral if found, otherwise continue normally. +	 */ +	if (referral_walks_count == 0) { +		int refrc = expand_dfs_referral(xid, ses, volume_info, cifs_sb, +						false); +		if (!refrc) { +			referral_walks_count++; +			goto try_mount_again; +		} +	} +#endif + +	/* check if a whole path is not remote */ +	if (!rc && tcon) { +		if (!server->ops->is_path_accessible) { +			rc = -ENOSYS; +			goto mount_fail_check; +		} +		/* +		 * cifs_build_path_to_root works only when we have a valid tcon +		 */ +		full_path = cifs_build_path_to_root(volume_info, cifs_sb, tcon);  		if (full_path == NULL) {  			rc = -ENOMEM;  			goto mount_fail_check;  		} -		rc = is_path_accessible(xid, tcon, cifs_sb, full_path); -		if (rc != -EREMOTE) { +		rc = server->ops->is_path_accessible(xid, tcon, cifs_sb, +						     full_path); +		if (rc != 0 && rc != -EREMOTE) {  			kfree(full_path);  			goto mount_fail_check;  		} @@ -2842,50 +3557,14 @@ remote_path_check:  			rc = -ELOOP;  			goto mount_fail_check;  		} -		/* convert forward to back slashes in prepath here if needed */ -		if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) == 0) -			convert_delimiter(cifs_sb->prepath, -					CIFS_DIR_SEP(cifs_sb)); -		full_path = build_unc_path_to_root(volume_info, cifs_sb); -		if (IS_ERR(full_path)) { -			rc = PTR_ERR(full_path); -			goto mount_fail_check; -		} - -		cFYI(1, "Getting referral for: %s", full_path); -		rc = get_dfs_path(xid, pSesInfo , full_path + 1, -			cifs_sb->local_nls, &num_referrals, &referrals, -			cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR); -		if (!rc && num_referrals > 0) { -			char *fake_devname = NULL; - -			if (mount_data != mount_data_global) -				kfree(mount_data); - -			mount_data = cifs_compose_mount_options( -					cifs_sb->mountdata, full_path + 1, -					referrals, &fake_devname); - -			free_dfs_info_array(referrals, num_referrals); -			kfree(fake_devname); -			kfree(full_path); -			if (IS_ERR(mount_data)) { -				rc = PTR_ERR(mount_data); -				mount_data = NULL; -				goto mount_fail_check; -			} - -			if (tcon) -				cifs_put_tcon(tcon); -			else if (pSesInfo) -				cifs_put_smb_ses(pSesInfo); +		rc = expand_dfs_referral(xid, ses, volume_info, cifs_sb, true); -			cleanup_volume_info(&volume_info); +		if (!rc) {  			referral_walks_count++; -			FreeXid(xid);  			goto try_mount_again;  		} +		goto mount_fail_check;  #else /* No DFS support, return error on mount */  		rc = -EOPNOTSUPP;  #endif @@ -2901,7 +3580,7 @@ remote_path_check:  		goto mount_fail_check;  	} -	tlink->tl_uid = pSesInfo->linux_uid; +	tlink->tl_uid = ses->linux_uid;  	tlink->tl_tcon = tcon;  	tlink->tl_time = jiffies;  	set_bit(TCON_LINK_MASTER, &tlink->tl_flags); @@ -2912,39 +3591,35 @@ remote_path_check:  	tlink_rb_insert(&cifs_sb->tlink_tree, tlink);  	spin_unlock(&cifs_sb->tlink_tree_lock); -	queue_delayed_work(system_nrt_wq, &cifs_sb->prune_tlinks, +	queue_delayed_work(cifsiod_wq, &cifs_sb->prune_tlinks,  				TLINK_IDLE_EXPIRE);  mount_fail_check:  	/* on error free sesinfo and tcon struct if needed */  	if (rc) { -		if (mount_data != mount_data_global) -			kfree(mount_data);  		/* If find_unc succeeded then rc == 0 so we can not end */ -		/* up accidently freeing someone elses tcon struct */ +		/* up accidentally freeing someone elses tcon struct */  		if (tcon)  			cifs_put_tcon(tcon); -		else if (pSesInfo) -			cifs_put_smb_ses(pSesInfo); +		else if (ses) +			cifs_put_smb_ses(ses);  		else -			cifs_put_tcp_session(srvTcp); -		goto out; +			cifs_put_tcp_session(server); +		bdi_destroy(&cifs_sb->bdi);  	} -	/* volume_info->password is freed above when existing session found -	(in which case it is not needed anymore) but when new sesion is created -	the password ptr is put in the new session structure (in which case the -	password will be freed at unmount time) */  out: -	/* zero out password before freeing */ -	cleanup_volume_info(&volume_info); -	FreeXid(xid); +	free_xid(xid);  	return rc;  } +/* + * Issue a TREE_CONNECT request. Note that for IPC$ shares, that the tcon + * pointer may be NULL. + */  int -CIFSTCon(unsigned int xid, struct cifsSesInfo *ses, -	 const char *tree, struct cifsTconInfo *tcon, +CIFSTCon(const unsigned int xid, struct cifs_ses *ses, +	 const char *tree, struct cifs_tcon *tcon,  	 const struct nls_table *nls_codepage)  {  	struct smb_hdr *smb_buffer; @@ -2953,8 +3628,8 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,  	TCONX_RSP *pSMBr;  	unsigned char *bcc_ptr;  	int rc = 0; -	int length, bytes_left; -	__u16 count; +	int length; +	__u16 bytes_left, count;  	if (ses == NULL)  		return -EIO; @@ -2968,7 +3643,7 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,  	header_assemble(smb_buffer, SMB_COM_TREE_CONNECT_ANDX,  			NULL /*no tid */ , 4 /*wct */ ); -	smb_buffer->Mid = GetNextMid(ses->server); +	smb_buffer->Mid = get_next_mid(ses->server);  	smb_buffer->Uid = ses->Suid;  	pSMB = (TCONX_REQ *) smb_buffer;  	pSMBr = (TCONX_RSP *) smb_buffer_response; @@ -2976,13 +3651,13 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,  	pSMB->AndXCommand = 0xFF;  	pSMB->Flags = cpu_to_le16(TCON_EXTENDED_SECINFO);  	bcc_ptr = &pSMB->Password[0]; -	if ((ses->server->secMode) & SECMODE_USER) { +	if (!tcon || (ses->server->sec_mode & SECMODE_USER)) {  		pSMB->PasswordLength = cpu_to_le16(1);	/* minimum */  		*bcc_ptr = 0; /* password is null byte */  		bcc_ptr++;              /* skip password */  		/* already aligned so no need to do it below */  	} else { -		pSMB->PasswordLength = cpu_to_le16(CIFS_SESS_KEY_SIZE); +		pSMB->PasswordLength = cpu_to_le16(CIFS_AUTH_RESP_SIZE);  		/* BB FIXME add code to fail this if NTLMv2 or Kerberos  		   specified as required (when that support is added to  		   the vfs in the future) as only NTLM or the much @@ -2991,16 +3666,17 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,  		   NTLMv2 password here) */  #ifdef CONFIG_CIFS_WEAK_PW_HASH  		if ((global_secflags & CIFSSEC_MAY_LANMAN) && -		    (ses->server->secType == LANMAN)) +		    (ses->sectype == LANMAN))  			calc_lanman_hash(tcon->password, ses->server->cryptkey, -					 ses->server->secMode & +					 ses->server->sec_mode &  					    SECMODE_PW_ENCRYPT ? true : false,  					 bcc_ptr);  		else  #endif /* CIFS_WEAK_PW_HASH */ -		SMBNTencrypt(tcon->password, ses->server->cryptkey, bcc_ptr); +		rc = SMBNTencrypt(tcon->password, ses->server->cryptkey, +					bcc_ptr, nls_codepage); -		bcc_ptr += CIFS_SESS_KEY_SIZE; +		bcc_ptr += CIFS_AUTH_RESP_SIZE;  		if (ses->capabilities & CAP_UNICODE) {  			/* must align unicode strings */  			*bcc_ptr = 0; /* null byte password */ @@ -3008,8 +3684,7 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,  		}  	} -	if (ses->server->secMode & -			(SECMODE_SIGN_REQUIRED | SECMODE_SIGN_ENABLED)) +	if (ses->server->sign)  		smb_buffer->Flags2 |= SMBFLG2_SECURITY_SIGNATURE;  	if (ses->capabilities & CAP_STATUS32) { @@ -3021,7 +3696,7 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,  	if (ses->capabilities & CAP_UNICODE) {  		smb_buffer->Flags2 |= SMBFLG2_UNICODE;  		length = -		    cifs_strtoUCS((__le16 *) bcc_ptr, tree, +		    cifs_strtoUTF16((__le16 *) bcc_ptr, tree,  			6 /* max utf8 char length in bytes */ *  			(/* server len*/ + 256 /* share len */), nls_codepage);  		bcc_ptr += 2 * length;	/* convert num 16 bit words to bytes */ @@ -3034,11 +3709,12 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,  	bcc_ptr += strlen("?????");  	bcc_ptr += 1;  	count = bcc_ptr - &pSMB->Password[0]; -	pSMB->hdr.smb_buf_length += count; +	pSMB->hdr.smb_buf_length = cpu_to_be32(be32_to_cpu( +					pSMB->hdr.smb_buf_length) + count);  	pSMB->ByteCount = cpu_to_le16(count);  	rc = SendReceive(xid, ses, smb_buffer, smb_buffer_response, &length, -			 CIFS_STD_OP); +			 0);  	/* above now done in SendReceive */  	if ((rc == 0) && (tcon != NULL)) { @@ -3048,7 +3724,7 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,  		tcon->need_reconnect = false;  		tcon->tid = smb_buffer_response->Tid;  		bcc_ptr = pByteArea(smb_buffer_response); -		bytes_left = BCC(smb_buffer_response); +		bytes_left = get_bcc(smb_buffer_response);  		length = strnlen(bcc_ptr, bytes_left - 2);  		if (smb_buffer->Flags2 & SMBFLG2_UNICODE)  			is_unicode = true; @@ -3060,26 +3736,26 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,  		if (length == 3) {  			if ((bcc_ptr[0] == 'I') && (bcc_ptr[1] == 'P') &&  			    (bcc_ptr[2] == 'C')) { -				cFYI(1, "IPC connection"); +				cifs_dbg(FYI, "IPC connection\n");  				tcon->ipc = 1;  			}  		} else if (length == 2) {  			if ((bcc_ptr[0] == 'A') && (bcc_ptr[1] == ':')) {  				/* the most common case */ -				cFYI(1, "disk share connection"); +				cifs_dbg(FYI, "disk share connection\n");  			}  		}  		bcc_ptr += length + 1;  		bytes_left -= (length + 1); -		strncpy(tcon->treeName, tree, MAX_TREE_SIZE); +		strlcpy(tcon->treeName, tree, sizeof(tcon->treeName));  		/* mostly informational -- no need to fail on error here */  		kfree(tcon->nativeFileSystem); -		tcon->nativeFileSystem = cifs_strndup_from_ucs(bcc_ptr, +		tcon->nativeFileSystem = cifs_strndup_from_utf16(bcc_ptr,  						      bytes_left, is_unicode,  						      nls_codepage); -		cFYI(1, "nativeFileSystem=%s", tcon->nativeFileSystem); +		cifs_dbg(FYI, "nativeFileSystem=%s\n", tcon->nativeFileSystem);  		if ((smb_buffer_response->WordCount == 3) ||  			 (smb_buffer_response->WordCount == 7)) @@ -3087,7 +3763,7 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,  			tcon->Flags = le16_to_cpu(pSMBr->OptionalSupport);  		else  			tcon->Flags = 0; -		cFYI(1, "Tcon flags: 0x%x ", tcon->Flags); +		cifs_dbg(FYI, "Tcon flags: 0x%x\n", tcon->Flags);  	} else if ((rc == 0) && tcon == NULL) {  		/* all we need to save for IPC$ connection */  		ses->ipc_tid = smb_buffer_response->Tid; @@ -3097,13 +3773,19 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,  	return rc;  } -int -cifs_umount(struct super_block *sb, struct cifs_sb_info *cifs_sb) +static void delayed_free(struct rcu_head *p) +{ +	struct cifs_sb_info *sbi = container_of(p, struct cifs_sb_info, rcu); +	unload_nls(sbi->local_nls); +	kfree(sbi); +} + +void +cifs_umount(struct cifs_sb_info *cifs_sb)  {  	struct rb_root *root = &cifs_sb->tlink_tree;  	struct rb_node *node;  	struct tcon_link *tlink; -	char *tmp;  	cancel_delayed_work_sync(&cifs_sb->prune_tlinks); @@ -3120,105 +3802,87 @@ cifs_umount(struct super_block *sb, struct cifs_sb_info *cifs_sb)  	}  	spin_unlock(&cifs_sb->tlink_tree_lock); -	tmp = cifs_sb->prepath; -	cifs_sb->prepathlen = 0; -	cifs_sb->prepath = NULL; -	kfree(tmp); - -	return 0; +	bdi_destroy(&cifs_sb->bdi); +	kfree(cifs_sb->mountdata); +	call_rcu(&cifs_sb->rcu, delayed_free);  } -int cifs_negotiate_protocol(unsigned int xid, struct cifsSesInfo *ses) +int +cifs_negotiate_protocol(const unsigned int xid, struct cifs_ses *ses)  {  	int rc = 0;  	struct TCP_Server_Info *server = ses->server; +	if (!server->ops->need_neg || !server->ops->negotiate) +		return -ENOSYS; +  	/* only send once per connect */ -	if (server->maxBuf != 0) +	if (!server->ops->need_neg(server))  		return 0; -	rc = CIFSSMBNegotiate(xid, ses); -	if (rc == -EAGAIN) { -		/* retry only once on 1st time connection */ -		rc = CIFSSMBNegotiate(xid, ses); -		if (rc == -EAGAIN) -			rc = -EHOSTDOWN; -	} +	set_credits(server, 1); + +	rc = server->ops->negotiate(xid, ses);  	if (rc == 0) {  		spin_lock(&GlobalMid_Lock); -		if (server->tcpStatus != CifsExiting) +		if (server->tcpStatus == CifsNeedNegotiate)  			server->tcpStatus = CifsGood;  		else  			rc = -EHOSTDOWN;  		spin_unlock(&GlobalMid_Lock); -  	}  	return rc;  } - -int cifs_setup_session(unsigned int xid, struct cifsSesInfo *ses, -			struct nls_table *nls_info) +int +cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, +		   struct nls_table *nls_info)  { -	int rc = 0; +	int rc = -ENOSYS;  	struct TCP_Server_Info *server = ses->server; -	ses->flags = 0;  	ses->capabilities = server->capabilities;  	if (linuxExtEnabled == 0) -		ses->capabilities &= (~CAP_UNIX); +		ses->capabilities &= (~server->vals->cap_unix); -	cFYI(1, "Security Mode: 0x%x Capabilities: 0x%x TimeAdjust: %d", -		 server->secMode, server->capabilities, server->timeAdj); - -	rc = CIFS_SessSetup(xid, ses, nls_info); -	if (rc) { -		cERROR(1, "Send error in SessSetup = %d", rc); -	} else { -		mutex_lock(&ses->server->srv_mutex); -		if (!server->session_estab) { -			server->session_key.response = ses->auth_key.response; -			server->session_key.len = ses->auth_key.len; -			server->sequence_number = 0x2; -			server->session_estab = true; -			ses->auth_key.response = NULL; -		} -		mutex_unlock(&server->srv_mutex); +	cifs_dbg(FYI, "Security Mode: 0x%x Capabilities: 0x%x TimeAdjust: %d\n", +		 server->sec_mode, server->capabilities, server->timeAdj); -		cFYI(1, "CIFS Session Established successfully"); -		spin_lock(&GlobalMid_Lock); -		ses->status = CifsGood; -		ses->need_reconnect = false; -		spin_unlock(&GlobalMid_Lock); -	} +	if (server->ops->sess_setup) +		rc = server->ops->sess_setup(xid, ses, nls_info); -	kfree(ses->auth_key.response); -	ses->auth_key.response = NULL; -	ses->auth_key.len = 0; -	kfree(ses->ntlmssp); -	ses->ntlmssp = NULL; +	if (rc) +		cifs_dbg(VFS, "Send error in SessSetup = %d\n", rc);  	return rc;  } -static struct cifsTconInfo * -cifs_construct_tcon(struct cifs_sb_info *cifs_sb, uid_t fsuid) +static int +cifs_set_vol_auth(struct smb_vol *vol, struct cifs_ses *ses) +{ +	vol->sectype = ses->sectype; + +	/* krb5 is special, since we don't need username or pw */ +	if (vol->sectype == Kerberos) +		return 0; + +	return cifs_set_cifscreds(vol, ses); +} + +static struct cifs_tcon * +cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid)  { -	struct cifsTconInfo *master_tcon = cifs_sb_master_tcon(cifs_sb); -	struct cifsSesInfo *ses; -	struct cifsTconInfo *tcon = NULL; +	int rc; +	struct cifs_tcon *master_tcon = cifs_sb_master_tcon(cifs_sb); +	struct cifs_ses *ses; +	struct cifs_tcon *tcon = NULL;  	struct smb_vol *vol_info; -	char username[MAX_USERNAME_SIZE + 1];  	vol_info = kzalloc(sizeof(*vol_info), GFP_KERNEL); -	if (vol_info == NULL) { -		tcon = ERR_PTR(-ENOMEM); -		goto out; -	} +	if (vol_info == NULL) +		return ERR_PTR(-ENOMEM); -	snprintf(username, MAX_USERNAME_SIZE, "krb50x%x", fsuid); -	vol_info->username = username;  	vol_info->local_nls = cifs_sb->local_nls;  	vol_info->linux_uid = fsuid;  	vol_info->cred_uid = fsuid; @@ -3227,9 +3891,14 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, uid_t fsuid)  	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; +	vol_info->sign = master_tcon->ses->sign; -	/* FIXME: allow for other secFlg settings */ -	vol_info->secFlg = CIFSSEC_MUST_KRB5; +	rc = cifs_set_vol_auth(vol_info, master_tcon->ses); +	if (rc) { +		tcon = ERR_PTR(rc); +		goto out; +	}  	/* get a reference for the same TCP session */  	spin_lock(&cifs_tcp_ses_lock); @@ -3238,7 +3907,7 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, uid_t fsuid)  	ses = cifs_get_smb_ses(master_tcon->ses->server, vol_info);  	if (IS_ERR(ses)) { -		tcon = (struct cifsTconInfo *)ses; +		tcon = (struct cifs_tcon *)ses;  		cifs_put_tcp_session(master_tcon->ses->server);  		goto out;  	} @@ -3249,21 +3918,17 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, uid_t fsuid)  		goto out;  	} -	if (ses->capabilities & CAP_UNIX) +	if (cap_unix(ses))  		reset_cifs_unix_caps(0, tcon, NULL, vol_info);  out: +	kfree(vol_info->username); +	kfree(vol_info->password);  	kfree(vol_info);  	return tcon;  } -static inline struct tcon_link * -cifs_sb_master_tlink(struct cifs_sb_info *cifs_sb) -{ -	return cifs_sb->master_tlink; -} - -struct cifsTconInfo * +struct cifs_tcon *  cifs_sb_master_tcon(struct cifs_sb_info *cifs_sb)  {  	return tlink_tcon(cifs_sb_master_tlink(cifs_sb)); @@ -3278,7 +3943,7 @@ cifs_sb_tcon_pending_wait(void *unused)  /* find and return a tlink with given uid */  static struct tcon_link * -tlink_rb_search(struct rb_root *root, uid_t uid) +tlink_rb_search(struct rb_root *root, kuid_t uid)  {  	struct rb_node *node = root->rb_node;  	struct tcon_link *tlink; @@ -3286,9 +3951,9 @@ tlink_rb_search(struct rb_root *root, uid_t uid)  	while (node) {  		tlink = rb_entry(node, struct tcon_link, tl_rbnode); -		if (tlink->tl_uid > uid) +		if (uid_gt(tlink->tl_uid, uid))  			node = node->rb_left; -		else if (tlink->tl_uid < uid) +		else if (uid_lt(tlink->tl_uid, uid))  			node = node->rb_right;  		else  			return tlink; @@ -3307,7 +3972,7 @@ tlink_rb_insert(struct rb_root *root, struct tcon_link *new_tlink)  		tlink = rb_entry(*new, struct tcon_link, tl_rbnode);  		parent = *new; -		if (tlink->tl_uid > new_tlink->tl_uid) +		if (uid_gt(tlink->tl_uid, new_tlink->tl_uid))  			new = &((*new)->rb_left);  		else  			new = &((*new)->rb_right); @@ -3337,7 +4002,7 @@ struct tcon_link *  cifs_sb_tlink(struct cifs_sb_info *cifs_sb)  {  	int ret; -	uid_t fsuid = current_fsuid(); +	kuid_t fsuid = current_fsuid();  	struct tcon_link *tlink, *newtlink;  	if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MULTIUSER)) @@ -3450,6 +4115,6 @@ cifs_prune_tlinks(struct work_struct *work)  	}  	spin_unlock(&cifs_sb->tlink_tree_lock); -	queue_delayed_work(system_nrt_wq, &cifs_sb->prune_tlinks, +	queue_delayed_work(cifsiod_wq, &cifs_sb->prune_tlinks,  				TLINK_IDLE_EXPIRE);  }  | 
