Commit 87dbe42a authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'for-next' of git://git.samba.org/sfrench/cifs-2.6

Pull cifs fixes from Steve French:
 "Including:

   - nine bug fixes for stable. Some of these we found at the recent two
     weeks of SMB3 test events/plugfests.

   - significant improvements in reconnection (e.g. if server or network
     crashes) especially when mounted with "persistenthandles" or to
     server which advertises Continuous Availability on the share.

   - a new mount option "idsfromsid" which improves POSIX compatibility
     in some cases (when winbind not configured e.g.) by better (and
     faster) fetching uid/gid from acl (when "cifsacl" mount option is
     enabled). NB: we are almost complete work on "cifsacl" (querying
     mode/uid/gid from ACL) for SMB3, but SMB3 support for cifsacl is
     not included in this set.

   - improved handling for SMB3 "credits" (even if server is buggy)

  Still working on two sets of changes:

   - cifsacl enablement for SMB3

   - cleanup of RFC1001 length calculation (so we can handle encryption
     and multichannel and RDMA)

  And a couple of new bugs were reported recently (unrelated to above)
  so will probably have another merge request next week"

* 'for-next' of git://git.samba.org/sfrench/cifs-2.6: (21 commits)
  CIFS: Retrieve uid and gid from special sid if enabled
  CIFS: Add new mount option to set owner uid and gid from special sids in acl
  CIFS: Reset read oplock to NONE if we have mandatory locks after reopen
  CIFS: Fix persistent handles re-opening on reconnect
  SMB2: Separate RawNTLMSSP authentication from SMB2_sess_setup
  SMB2: Separate Kerberos authentication from SMB2_sess_setup
  Expose cifs module parameters in sysfs
  Cleanup missing frees on some ioctls
  Enable previous version support
  Do not send SMB3 SET_INFO request if nothing is changing
  SMB3: Add mount parameter to allow user to override max credits
  fs/cifs: reopen persistent handles on reconnect
  Clarify locking of cifs file and tcon structures and make more granular
  Fix regression which breaks DFS mounting
  fs/cifs: keep guid when assigning fid to fileinfo
  SMB3: GUIDs should be constructed as random but valid uuids
  Set previous session id correctly on SMB3 reconnect
  cifs: Limit the overall credit acquired
  Display number of credits available
  Add way to query creation time of file via cifs xattr
  ...
parents d3304cad 3514de3f
...@@ -152,6 +152,7 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v) ...@@ -152,6 +152,7 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
list_for_each(tmp1, &cifs_tcp_ses_list) { list_for_each(tmp1, &cifs_tcp_ses_list) {
server = list_entry(tmp1, struct TCP_Server_Info, server = list_entry(tmp1, struct TCP_Server_Info,
tcp_ses_list); tcp_ses_list);
seq_printf(m, "\nNumber of credits: %d", server->credits);
i++; i++;
list_for_each(tmp2, &server->smb_ses_list) { list_for_each(tmp2, &server->smb_ses_list) {
ses = list_entry(tmp2, struct cifs_ses, ses = list_entry(tmp2, struct cifs_ses,
......
...@@ -49,6 +49,7 @@ ...@@ -49,6 +49,7 @@
#define CIFS_MOUNT_USE_PREFIX_PATH 0x1000000 /* make subpath with unaccessible #define CIFS_MOUNT_USE_PREFIX_PATH 0x1000000 /* make subpath with unaccessible
* root mountable * root mountable
*/ */
#define CIFS_MOUNT_UID_FROM_ACL 0x2000000 /* try to get UID via special SID */
struct cifs_sb_info { struct cifs_sb_info {
struct rb_root tlink_tree; struct rb_root tlink_tree;
......
...@@ -36,7 +36,15 @@ struct smb_mnt_fs_info { ...@@ -36,7 +36,15 @@ struct smb_mnt_fs_info {
__u64 cifs_posix_caps; __u64 cifs_posix_caps;
} __packed; } __packed;
struct smb_snapshot_array {
__u32 number_of_snapshots;
__u32 number_of_snapshots_returned;
__u32 snapshot_array_size;
/* snapshots[]; */
} __packed;
#define CIFS_IOCTL_MAGIC 0xCF #define CIFS_IOCTL_MAGIC 0xCF
#define CIFS_IOC_COPYCHUNK_FILE _IOW(CIFS_IOCTL_MAGIC, 3, int) #define CIFS_IOC_COPYCHUNK_FILE _IOW(CIFS_IOCTL_MAGIC, 3, int)
#define CIFS_IOC_SET_INTEGRITY _IO(CIFS_IOCTL_MAGIC, 4) #define CIFS_IOC_SET_INTEGRITY _IO(CIFS_IOCTL_MAGIC, 4)
#define CIFS_IOC_GET_MNT_INFO _IOR(CIFS_IOCTL_MAGIC, 5, struct smb_mnt_fs_info) #define CIFS_IOC_GET_MNT_INFO _IOR(CIFS_IOCTL_MAGIC, 5, struct smb_mnt_fs_info)
#define CIFS_ENUMERATE_SNAPSHOTS _IOR(CIFS_IOCTL_MAGIC, 6, struct smb_snapshot_array)
...@@ -42,6 +42,35 @@ static const struct cifs_sid sid_authusers = { ...@@ -42,6 +42,35 @@ static const struct cifs_sid sid_authusers = {
/* group users */ /* group users */
static const struct cifs_sid sid_user = {1, 2 , {0, 0, 0, 0, 0, 5}, {} }; static const struct cifs_sid sid_user = {1, 2 , {0, 0, 0, 0, 0, 5}, {} };
/* S-1-22-1 Unmapped Unix users */
static const struct cifs_sid sid_unix_users = {1, 1, {0, 0, 0, 0, 0, 22},
{cpu_to_le32(1), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} };
/* S-1-22-2 Unmapped Unix groups */
static const struct cifs_sid sid_unix_groups = { 1, 1, {0, 0, 0, 0, 0, 22},
{cpu_to_le32(2), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} };
/*
* See http://technet.microsoft.com/en-us/library/hh509017(v=ws.10).aspx
*/
/* S-1-5-88 MS NFS and Apple style UID/GID/mode */
/* S-1-5-88-1 Unix uid */
static const struct cifs_sid sid_unix_NFS_users = { 1, 2, {0, 0, 0, 0, 0, 5},
{cpu_to_le32(88),
cpu_to_le32(1), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} };
/* S-1-5-88-2 Unix gid */
static const struct cifs_sid sid_unix_NFS_groups = { 1, 2, {0, 0, 0, 0, 0, 5},
{cpu_to_le32(88),
cpu_to_le32(2), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} };
/* S-1-5-88-3 Unix mode */
static const struct cifs_sid sid_unix_NFS_mode = { 1, 2, {0, 0, 0, 0, 0, 5},
{cpu_to_le32(88),
cpu_to_le32(3), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} };
static const struct cred *root_cred; static const struct cred *root_cred;
static int static int
...@@ -183,6 +212,62 @@ compare_sids(const struct cifs_sid *ctsid, const struct cifs_sid *cwsid) ...@@ -183,6 +212,62 @@ compare_sids(const struct cifs_sid *ctsid, const struct cifs_sid *cwsid)
return 0; /* sids compare/match */ return 0; /* sids compare/match */
} }
static bool
is_well_known_sid(const struct cifs_sid *psid, uint32_t *puid, bool is_group)
{
int i;
int num_subauth;
const struct cifs_sid *pwell_known_sid;
if (!psid || (puid == NULL))
return false;
num_subauth = psid->num_subauth;
/* check if Mac (or Windows NFS) vs. Samba format for Unix owner SID */
if (num_subauth == 2) {
if (is_group)
pwell_known_sid = &sid_unix_groups;
else
pwell_known_sid = &sid_unix_users;
} else if (num_subauth == 3) {
if (is_group)
pwell_known_sid = &sid_unix_NFS_groups;
else
pwell_known_sid = &sid_unix_NFS_users;
} else
return false;
/* compare the revision */
if (psid->revision != pwell_known_sid->revision)
return false;
/* compare all of the six auth values */
for (i = 0; i < NUM_AUTHS; ++i) {
if (psid->authority[i] != pwell_known_sid->authority[i]) {
cifs_dbg(FYI, "auth %d did not match\n", i);
return false;
}
}
if (num_subauth == 2) {
if (psid->sub_auth[0] != pwell_known_sid->sub_auth[0])
return false;
*puid = le32_to_cpu(psid->sub_auth[1]);
} else /* 3 subauths, ie Windows/Mac style */ {
*puid = le32_to_cpu(psid->sub_auth[0]);
if ((psid->sub_auth[0] != pwell_known_sid->sub_auth[0]) ||
(psid->sub_auth[1] != pwell_known_sid->sub_auth[1]))
return false;
*puid = le32_to_cpu(psid->sub_auth[2]);
}
cifs_dbg(FYI, "Unix UID %d returned from SID\n", *puid);
return true; /* well known sid found, uid returned */
}
static void static void
cifs_copy_sid(struct cifs_sid *dst, const struct cifs_sid *src) cifs_copy_sid(struct cifs_sid *dst, const struct cifs_sid *src)
{ {
...@@ -276,6 +361,43 @@ sid_to_id(struct cifs_sb_info *cifs_sb, struct cifs_sid *psid, ...@@ -276,6 +361,43 @@ sid_to_id(struct cifs_sb_info *cifs_sb, struct cifs_sid *psid,
return -EIO; return -EIO;
} }
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UID_FROM_ACL) {
uint32_t unix_id;
bool is_group;
if (sidtype != SIDOWNER)
is_group = true;
else
is_group = false;
if (is_well_known_sid(psid, &unix_id, is_group) == false)
goto try_upcall_to_get_id;
if (is_group) {
kgid_t gid;
gid_t id;
id = (gid_t)unix_id;
gid = make_kgid(&init_user_ns, id);
if (gid_valid(gid)) {
fgid = gid;
goto got_valid_id;
}
} else {
kuid_t uid;
uid_t id;
id = (uid_t)unix_id;
uid = make_kuid(&init_user_ns, id);
if (uid_valid(uid)) {
fuid = uid;
goto got_valid_id;
}
}
/* If unable to find uid/gid easily from SID try via upcall */
}
try_upcall_to_get_id:
sidstr = sid_to_key_str(psid, sidtype); sidstr = sid_to_key_str(psid, sidtype);
if (!sidstr) if (!sidstr)
return -ENOMEM; return -ENOMEM;
...@@ -329,6 +451,7 @@ sid_to_id(struct cifs_sb_info *cifs_sb, struct cifs_sid *psid, ...@@ -329,6 +451,7 @@ sid_to_id(struct cifs_sb_info *cifs_sb, struct cifs_sid *psid,
* Note that we return 0 here unconditionally. If the mapping * Note that we return 0 here unconditionally. If the mapping
* fails then we just fall back to using the mnt_uid/mnt_gid. * fails then we just fall back to using the mnt_uid/mnt_gid.
*/ */
got_valid_id:
if (sidtype == SIDOWNER) if (sidtype == SIDOWNER)
fattr->cf_uid = fuid; fattr->cf_uid = fuid;
else else
......
...@@ -64,15 +64,15 @@ unsigned int global_secflags = CIFSSEC_DEF; ...@@ -64,15 +64,15 @@ unsigned int global_secflags = CIFSSEC_DEF;
unsigned int sign_CIFS_PDUs = 1; unsigned int sign_CIFS_PDUs = 1;
static const struct super_operations cifs_super_ops; static const struct super_operations cifs_super_ops;
unsigned int CIFSMaxBufSize = CIFS_MAX_MSGSIZE; unsigned int CIFSMaxBufSize = CIFS_MAX_MSGSIZE;
module_param(CIFSMaxBufSize, uint, 0); module_param(CIFSMaxBufSize, uint, 0444);
MODULE_PARM_DESC(CIFSMaxBufSize, "Network buffer size (not including header). " MODULE_PARM_DESC(CIFSMaxBufSize, "Network buffer size (not including header). "
"Default: 16384 Range: 8192 to 130048"); "Default: 16384 Range: 8192 to 130048");
unsigned int cifs_min_rcv = CIFS_MIN_RCV_POOL; unsigned int cifs_min_rcv = CIFS_MIN_RCV_POOL;
module_param(cifs_min_rcv, uint, 0); module_param(cifs_min_rcv, uint, 0444);
MODULE_PARM_DESC(cifs_min_rcv, "Network buffers in pool. Default: 4 Range: " MODULE_PARM_DESC(cifs_min_rcv, "Network buffers in pool. Default: 4 Range: "
"1 to 64"); "1 to 64");
unsigned int cifs_min_small = 30; unsigned int cifs_min_small = 30;
module_param(cifs_min_small, uint, 0); module_param(cifs_min_small, uint, 0444);
MODULE_PARM_DESC(cifs_min_small, "Small network buffers in pool. Default: 30 " MODULE_PARM_DESC(cifs_min_small, "Small network buffers in pool. Default: 30 "
"Range: 2 to 256"); "Range: 2 to 256");
unsigned int cifs_max_pending = CIFS_MAX_REQ; unsigned int cifs_max_pending = CIFS_MAX_REQ;
...@@ -271,7 +271,7 @@ cifs_alloc_inode(struct super_block *sb) ...@@ -271,7 +271,7 @@ cifs_alloc_inode(struct super_block *sb)
cifs_inode->createtime = 0; cifs_inode->createtime = 0;
cifs_inode->epoch = 0; cifs_inode->epoch = 0;
#ifdef CONFIG_CIFS_SMB2 #ifdef CONFIG_CIFS_SMB2
get_random_bytes(cifs_inode->lease_key, SMB2_LEASE_KEY_SIZE); generate_random_uuid(cifs_inode->lease_key);
#endif #endif
/* /*
* Can not set i_flags here - they get immediately overwritten to zero * Can not set i_flags here - they get immediately overwritten to zero
...@@ -469,6 +469,8 @@ cifs_show_options(struct seq_file *s, struct dentry *root) ...@@ -469,6 +469,8 @@ cifs_show_options(struct seq_file *s, struct dentry *root)
seq_puts(s, ",posixpaths"); seq_puts(s, ",posixpaths");
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SET_UID) if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SET_UID)
seq_puts(s, ",setuids"); seq_puts(s, ",setuids");
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UID_FROM_ACL)
seq_puts(s, ",idsfromsid");
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM) if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM)
seq_puts(s, ",serverino"); seq_puts(s, ",serverino");
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_RWPIDFORWARD) if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_RWPIDFORWARD)
...@@ -1262,7 +1264,6 @@ init_cifs(void) ...@@ -1262,7 +1264,6 @@ init_cifs(void)
GlobalTotalActiveXid = 0; GlobalTotalActiveXid = 0;
GlobalMaxActiveXid = 0; GlobalMaxActiveXid = 0;
spin_lock_init(&cifs_tcp_ses_lock); spin_lock_init(&cifs_tcp_ses_lock);
spin_lock_init(&cifs_file_list_lock);
spin_lock_init(&GlobalMid_Lock); spin_lock_init(&GlobalMid_Lock);
get_random_bytes(&cifs_lock_secret, sizeof(cifs_lock_secret)); get_random_bytes(&cifs_lock_secret, sizeof(cifs_lock_secret));
......
...@@ -75,6 +75,18 @@ ...@@ -75,6 +75,18 @@
#define SMB_ECHO_INTERVAL_MAX 600 #define SMB_ECHO_INTERVAL_MAX 600
#define SMB_ECHO_INTERVAL_DEFAULT 60 #define SMB_ECHO_INTERVAL_DEFAULT 60
/*
* Default number of credits to keep available for SMB3.
* This value is chosen somewhat arbitrarily. The Windows client
* defaults to 128 credits, the Windows server allows clients up to
* 512 credits (or 8K for later versions), and the NetApp server
* does not limit clients at all. Choose a high enough default value
* such that the client shouldn't limit performance, but allow mount
* to override (until you approach 64K, where we limit credits to 65000
* to reduce possibility of seeing more server credit overflow bugs.
*/
#define SMB2_MAX_CREDITS_AVAILABLE 32000
#include "cifspdu.h" #include "cifspdu.h"
#ifndef XATTR_DOS_ATTRIB #ifndef XATTR_DOS_ATTRIB
...@@ -376,6 +388,8 @@ struct smb_version_operations { ...@@ -376,6 +388,8 @@ struct smb_version_operations {
int (*calc_signature)(struct smb_rqst *, struct TCP_Server_Info *); int (*calc_signature)(struct smb_rqst *, struct TCP_Server_Info *);
int (*set_integrity)(const unsigned int, struct cifs_tcon *tcon, int (*set_integrity)(const unsigned int, struct cifs_tcon *tcon,
struct cifsFileInfo *src_file); struct cifsFileInfo *src_file);
int (*enum_snapshots)(const unsigned int xid, struct cifs_tcon *tcon,
struct cifsFileInfo *src_file, void __user *);
int (*query_mf_symlink)(unsigned int, struct cifs_tcon *, int (*query_mf_symlink)(unsigned int, struct cifs_tcon *,
struct cifs_sb_info *, const unsigned char *, struct cifs_sb_info *, const unsigned char *,
char *, unsigned int *); char *, unsigned int *);
...@@ -464,6 +478,7 @@ struct smb_vol { ...@@ -464,6 +478,7 @@ struct smb_vol {
bool retry:1; bool retry:1;
bool intr:1; bool intr:1;
bool setuids:1; bool setuids:1;
bool setuidfromacl:1;
bool override_uid:1; bool override_uid:1;
bool override_gid:1; bool override_gid:1;
bool dynperm:1; bool dynperm:1;
...@@ -510,6 +525,7 @@ struct smb_vol { ...@@ -510,6 +525,7 @@ struct smb_vol {
struct sockaddr_storage srcaddr; /* allow binding to a local IP */ struct sockaddr_storage srcaddr; /* allow binding to a local IP */
struct nls_table *local_nls; struct nls_table *local_nls;
unsigned int echo_interval; /* echo interval in secs */ unsigned int echo_interval; /* echo interval in secs */
unsigned int max_credits; /* smb3 max_credits 10 < credits < 60000 */
}; };
#define CIFS_MOUNT_MASK (CIFS_MOUNT_NO_PERM | CIFS_MOUNT_SET_UID | \ #define CIFS_MOUNT_MASK (CIFS_MOUNT_NO_PERM | CIFS_MOUNT_SET_UID | \
...@@ -567,7 +583,8 @@ struct TCP_Server_Info { ...@@ -567,7 +583,8 @@ struct TCP_Server_Info {
bool noblocksnd; /* use blocking sendmsg */ bool noblocksnd; /* use blocking sendmsg */
bool noautotune; /* do not autotune send buf sizes */ bool noautotune; /* do not autotune send buf sizes */
bool tcp_nodelay; bool tcp_nodelay;
int credits; /* send no more requests at once */ unsigned int credits; /* send no more requests at once */
unsigned int max_credits; /* can override large 32000 default at mnt */
unsigned int in_flight; /* number of requests on the wire to server */ unsigned int in_flight; /* number of requests on the wire to server */
spinlock_t req_lock; /* protect the two values above */ spinlock_t req_lock; /* protect the two values above */
struct mutex srv_mutex; struct mutex srv_mutex;
...@@ -833,6 +850,7 @@ struct cifs_tcon { ...@@ -833,6 +850,7 @@ struct cifs_tcon {
struct list_head tcon_list; struct list_head tcon_list;
int tc_count; int tc_count;
struct list_head openFileList; struct list_head openFileList;
spinlock_t open_file_lock; /* protects list above */
struct cifs_ses *ses; /* pointer to session associated with */ struct cifs_ses *ses; /* pointer to session associated with */
char treeName[MAX_TREE_SIZE + 1]; /* UNC name of resource in ASCII */ char treeName[MAX_TREE_SIZE + 1]; /* UNC name of resource in ASCII */
char *nativeFileSystem; char *nativeFileSystem;
...@@ -889,7 +907,7 @@ struct cifs_tcon { ...@@ -889,7 +907,7 @@ struct cifs_tcon {
#endif /* CONFIG_CIFS_STATS2 */ #endif /* CONFIG_CIFS_STATS2 */
__u64 bytes_read; __u64 bytes_read;
__u64 bytes_written; __u64 bytes_written;
spinlock_t stat_lock; spinlock_t stat_lock; /* protects the two fields above */
#endif /* CONFIG_CIFS_STATS */ #endif /* CONFIG_CIFS_STATS */
FILE_SYSTEM_DEVICE_INFO fsDevInfo; FILE_SYSTEM_DEVICE_INFO fsDevInfo;
FILE_SYSTEM_ATTRIBUTE_INFO fsAttrInfo; /* ok if fs name truncated */ FILE_SYSTEM_ATTRIBUTE_INFO fsAttrInfo; /* ok if fs name truncated */
...@@ -1040,20 +1058,24 @@ struct cifs_fid_locks { ...@@ -1040,20 +1058,24 @@ struct cifs_fid_locks {
}; };
struct cifsFileInfo { struct cifsFileInfo {
/* following two lists are protected by tcon->open_file_lock */
struct list_head tlist; /* pointer to next fid owned by tcon */ struct list_head tlist; /* pointer to next fid owned by tcon */
struct list_head flist; /* next fid (file instance) for this inode */ struct list_head flist; /* next fid (file instance) for this inode */
/* lock list below protected by cifsi->lock_sem */
struct cifs_fid_locks *llist; /* brlocks held by this fid */ struct cifs_fid_locks *llist; /* brlocks held by this fid */
kuid_t uid; /* allows finding which FileInfo structure */ kuid_t uid; /* allows finding which FileInfo structure */
__u32 pid; /* process id who opened file */ __u32 pid; /* process id who opened file */
struct cifs_fid fid; /* file id from remote */ struct cifs_fid fid; /* file id from remote */
struct list_head rlist; /* reconnect list */
/* BB add lock scope info here if needed */ ; /* BB add lock scope info here if needed */ ;
/* lock scope id (0 if none) */ /* lock scope id (0 if none) */
struct dentry *dentry; struct dentry *dentry;
unsigned int f_flags;
struct tcon_link *tlink; struct tcon_link *tlink;
unsigned int f_flags;
bool invalidHandle:1; /* file closed via session abend */ bool invalidHandle:1; /* file closed via session abend */
bool oplock_break_cancelled:1; bool oplock_break_cancelled:1;
int count; /* refcount protected by cifs_file_list_lock */ int count;
spinlock_t file_info_lock; /* protects four flag/count fields above */
struct mutex fh_mutex; /* prevents reopen race after dead ses*/ struct mutex fh_mutex; /* prevents reopen race after dead ses*/
struct cifs_search_info srch_inf; struct cifs_search_info srch_inf;
struct work_struct oplock_break; /* work for oplock breaks */ struct work_struct oplock_break; /* work for oplock breaks */
...@@ -1120,7 +1142,7 @@ struct cifs_writedata { ...@@ -1120,7 +1142,7 @@ struct cifs_writedata {
/* /*
* Take a reference on the file private data. Must be called with * Take a reference on the file private data. Must be called with
* cifs_file_list_lock held. * cfile->file_info_lock held.
*/ */
static inline void static inline void
cifsFileInfo_get_locked(struct cifsFileInfo *cifs_file) cifsFileInfo_get_locked(struct cifsFileInfo *cifs_file)
...@@ -1514,8 +1536,10 @@ require use of the stronger protocol */ ...@@ -1514,8 +1536,10 @@ require use of the stronger protocol */
* GlobalMid_Lock protects: * GlobalMid_Lock protects:
* list operations on pending_mid_q and oplockQ * list operations on pending_mid_q and oplockQ
* updates to XID counters, multiplex id and SMB sequence numbers * updates to XID counters, multiplex id and SMB sequence numbers
* cifs_file_list_lock protects: * tcp_ses_lock protects:
* list operations on tcp and SMB session lists and tCon lists * list operations on tcp and SMB session lists
* tcon->open_file_lock protects the list of open files hanging off the tcon
* cfile->file_info_lock protects counters and fields in cifs file struct
* f_owner.lock protects certain per file struct operations * f_owner.lock protects certain per file struct operations
* mapping->page_lock protects certain per page operations * mapping->page_lock protects certain per page operations
* *
...@@ -1547,18 +1571,12 @@ GLOBAL_EXTERN struct list_head cifs_tcp_ses_list; ...@@ -1547,18 +1571,12 @@ GLOBAL_EXTERN struct list_head cifs_tcp_ses_list;
* tcp session, and the list of tcon's per smb session. It also protects * tcp session, and the list of tcon's per smb session. It also protects
* the reference counters for the server, smb session, and tcon. Finally, * the reference counters for the server, smb session, and tcon. Finally,
* changes to the tcon->tidStatus should be done while holding this lock. * changes to the tcon->tidStatus should be done while holding this lock.
* generally the locks should be taken in order tcp_ses_lock before
* tcon->open_file_lock and that before file->file_info_lock since the
* structure order is cifs_socket-->cifs_ses-->cifs_tcon-->cifs_file
*/ */
GLOBAL_EXTERN spinlock_t cifs_tcp_ses_lock; GLOBAL_EXTERN spinlock_t cifs_tcp_ses_lock;
/*
* This lock protects the cifs_file->llist and cifs_file->flist
* list operations, and updates to some flags (cifs_file->invalidHandle)
* It will be moved to either use the tcon->stat_lock or equivalent later.
* If cifs_tcp_ses_lock and the lock below are both needed to be held, then
* the cifs_tcp_ses_lock must be grabbed first and released last.
*/
GLOBAL_EXTERN spinlock_t cifs_file_list_lock;
#ifdef CONFIG_CIFS_DNOTIFY_EXPERIMENTAL /* unused temporarily */ #ifdef CONFIG_CIFS_DNOTIFY_EXPERIMENTAL /* unused temporarily */
/* Outstanding dir notify requests */ /* Outstanding dir notify requests */
GLOBAL_EXTERN struct list_head GlobalDnotifyReqList; GLOBAL_EXTERN struct list_head GlobalDnotifyReqList;
......
...@@ -193,6 +193,8 @@ extern struct smb_vol *cifs_get_volume_info(char *mount_data, ...@@ -193,6 +193,8 @@ extern struct smb_vol *cifs_get_volume_info(char *mount_data,
extern int cifs_mount(struct cifs_sb_info *, struct smb_vol *); extern int cifs_mount(struct cifs_sb_info *, struct smb_vol *);
extern void cifs_umount(struct cifs_sb_info *); extern void cifs_umount(struct cifs_sb_info *);
extern void cifs_mark_open_files_invalid(struct cifs_tcon *tcon); extern void cifs_mark_open_files_invalid(struct cifs_tcon *tcon);
extern void cifs_reopen_persistent_handles(struct cifs_tcon *tcon);
extern bool cifs_find_lock_conflict(struct cifsFileInfo *cfile, __u64 offset, extern bool cifs_find_lock_conflict(struct cifsFileInfo *cfile, __u64 offset,
__u64 length, __u8 type, __u64 length, __u8 type,
struct cifsLockInfo **conf_lock, struct cifsLockInfo **conf_lock,
......
...@@ -98,13 +98,13 @@ cifs_mark_open_files_invalid(struct cifs_tcon *tcon) ...@@ -98,13 +98,13 @@ cifs_mark_open_files_invalid(struct cifs_tcon *tcon)
struct list_head *tmp1; struct list_head *tmp1;
/* list all files open on tree connection and mark them invalid */ /* list all files open on tree connection and mark them invalid */
spin_lock(&cifs_file_list_lock); spin_lock(&tcon->open_file_lock);
list_for_each_safe(tmp, tmp1, &tcon->openFileList) { list_for_each_safe(tmp, tmp1, &tcon->openFileList) {
open_file = list_entry(tmp, struct cifsFileInfo, tlist); open_file = list_entry(tmp, struct cifsFileInfo, tlist);
open_file->invalidHandle = true; open_file->invalidHandle = true;
open_file->oplock_break_cancelled = true; open_file->oplock_break_cancelled = true;
} }
spin_unlock(&cifs_file_list_lock); spin_unlock(&tcon->open_file_lock);
/* /*
* BB Add call to invalidate_inodes(sb) for all superblocks mounted * BB Add call to invalidate_inodes(sb) for all superblocks mounted
* to this tcon. * to this tcon.
......
...@@ -63,7 +63,6 @@ extern mempool_t *cifs_req_poolp; ...@@ -63,7 +63,6 @@ extern mempool_t *cifs_req_poolp;
#define TLINK_IDLE_EXPIRE (600 * HZ) #define TLINK_IDLE_EXPIRE (600 * HZ)
enum { enum {
/* Mount options that take no arguments */ /* Mount options that take no arguments */
Opt_user_xattr, Opt_nouser_xattr, Opt_user_xattr, Opt_nouser_xattr,
Opt_forceuid, Opt_noforceuid, Opt_forceuid, Opt_noforceuid,
...@@ -76,7 +75,7 @@ enum { ...@@ -76,7 +75,7 @@ enum {
Opt_noposixpaths, Opt_nounix, Opt_noposixpaths, Opt_nounix,
Opt_nocase, Opt_nocase,
Opt_brl, Opt_nobrl, Opt_brl, Opt_nobrl,
Opt_forcemandatorylock, Opt_setuids, Opt_forcemandatorylock, Opt_setuidfromacl, Opt_setuids,
Opt_nosetuids, Opt_dynperm, Opt_nodynperm, Opt_nosetuids, Opt_dynperm, Opt_nodynperm,
Opt_nohard, Opt_nosoft, Opt_nohard, Opt_nosoft,
Opt_nointr, Opt_intr, Opt_nointr, Opt_intr,
...@@ -95,7 +94,7 @@ enum { ...@@ -95,7 +94,7 @@ enum {
Opt_cruid, Opt_gid, Opt_file_mode, Opt_cruid, Opt_gid, Opt_file_mode,
Opt_dirmode, Opt_port, Opt_dirmode, Opt_port,
Opt_rsize, Opt_wsize, Opt_actimeo, Opt_rsize, Opt_wsize, Opt_actimeo,
Opt_echo_interval, Opt_echo_interval, Opt_max_credits,
/* Mount options which take string value */ /* Mount options which take string value */
Opt_user, Opt_pass, Opt_ip, Opt_user, Opt_pass, Opt_ip,
...@@ -148,6 +147,7 @@ static const match_table_t cifs_mount_option_tokens = { ...@@ -148,6 +147,7 @@ static const match_table_t cifs_mount_option_tokens = {
{ Opt_forcemandatorylock, "forcemand" }, { Opt_forcemandatorylock, "forcemand" },
{ Opt_setuids, "setuids" }, { Opt_setuids, "setuids" },
{ Opt_nosetuids, "nosetuids" }, { Opt_nosetuids, "nosetuids" },
{ Opt_setuidfromacl, "idsfromsid" },
{ Opt_dynperm, "dynperm" }, { Opt_dynperm, "dynperm" },
{ Opt_nodynperm, "nodynperm" }, { Opt_nodynperm, "nodynperm" },
{ Opt_nohard, "nohard" }, { Opt_nohard, "nohard" },
...@@ -190,6 +190,7 @@ static const match_table_t cifs_mount_option_tokens = { ...@@ -190,6 +190,7 @@ static const match_table_t cifs_mount_option_tokens = {
{ Opt_wsize, "wsize=%s" }, { Opt_wsize, "wsize=%s" },
{ Opt_actimeo, "actimeo=%s" }, { Opt_actimeo, "actimeo=%s" },
{ Opt_echo_interval, "echo_interval=%s" }, { Opt_echo_interval, "echo_interval=%s" },
{ Opt_max_credits, "max_credits=%s" },
{ Opt_blank_user, "user=" }, { Opt_blank_user, "user=" },
{ Opt_blank_user, "username=" }, { Opt_blank_user, "username=" },
...@@ -1376,6 +1377,9 @@ cifs_parse_mount_options(const char *mountdata, const char *devname, ...@@ -1376,6 +1377,9 @@ cifs_parse_mount_options(const char *mountdata, const char *devname,
case Opt_nosetuids: case Opt_nosetuids:
vol->setuids = 0; vol->setuids = 0;
break; break;
case Opt_setuidfromacl:
vol->setuidfromacl = 1;
break;
case Opt_dynperm: case Opt_dynperm:
vol->dynperm = true; vol->dynperm = true;
break; break;
...@@ -1586,6 +1590,15 @@ cifs_parse_mount_options(const char *mountdata, const char *devname, ...@@ -1586,6 +1590,15 @@ cifs_parse_mount_options(const char *mountdata, const char *devname,
} }
vol->echo_interval = option; vol->echo_interval = option;
break; break;
case Opt_max_credits:
if (get_option_ul(args, &option) || (option < 20) ||
(option > 60000)) {
cifs_dbg(VFS, "%s: Invalid max_credits value\n",
__func__);
goto cifs_parse_mount_err;
}
vol->max_credits = option;
break;
/* String Arguments */ /* String Arguments */
...@@ -2163,7 +2176,7 @@ cifs_get_tcp_session(struct smb_vol *volume_info) ...@@ -2163,7 +2176,7 @@ cifs_get_tcp_session(struct smb_vol *volume_info)
memcpy(&tcp_ses->dstaddr, &volume_info->dstaddr, memcpy(&tcp_ses->dstaddr, &volume_info->dstaddr,
sizeof(tcp_ses->dstaddr)); sizeof(tcp_ses->dstaddr));
#ifdef CONFIG_CIFS_SMB2 #ifdef CONFIG_CIFS_SMB2
get_random_bytes(tcp_ses->client_guid, SMB2_CLIENT_GUID_SIZE); generate_random_uuid(tcp_ses->client_guid);
#endif #endif
/* /*
* at this point we are the only ones with the pointer * at this point we are the only ones with the pointer
...@@ -3270,6 +3283,8 @@ int cifs_setup_cifs_sb(struct smb_vol *pvolume_info, ...@@ -3270,6 +3283,8 @@ int cifs_setup_cifs_sb(struct smb_vol *pvolume_info,
cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NO_PERM; cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NO_PERM;
if (pvolume_info->setuids) if (pvolume_info->setuids)
cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_SET_UID; cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_SET_UID;
if (pvolume_info->setuidfromacl)
cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_UID_FROM_ACL;
if (pvolume_info->server_ino) if (pvolume_info->server_ino)
cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_SERVER_INUM; cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_SERVER_INUM;
if (pvolume_info->remap) if (pvolume_info->remap)
...@@ -3598,7 +3613,11 @@ cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info) ...@@ -3598,7 +3613,11 @@ cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info)
bdi_destroy(&cifs_sb->bdi); bdi_destroy(&cifs_sb->bdi);
goto out; goto out;
} }
if ((volume_info->max_credits < 20) ||
(volume_info->max_credits > 60000))
server->max_credits = SMB2_MAX_CREDITS_AVAILABLE;
else
server->max_credits = volume_info->max_credits;
/* get a reference to a SMB session */ /* get a reference to a SMB session */
ses = cifs_get_smb_ses(server, volume_info); ses = cifs_get_smb_ses(server, volume_info);
if (IS_ERR(ses)) { if (IS_ERR(ses)) {
...@@ -3688,14 +3707,16 @@ cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info) ...@@ -3688,14 +3707,16 @@ cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info)
goto mount_fail_check; goto mount_fail_check;
} }
rc = cifs_are_all_path_components_accessible(server, if (rc != -EREMOTE) {
rc = cifs_are_all_path_components_accessible(server,
xid, tcon, cifs_sb, xid, tcon, cifs_sb,
full_path); full_path);
if (rc != 0) { if (rc != 0) {
cifs_dbg(VFS, "cannot query dirs between root and final path, " cifs_dbg(VFS, "cannot query dirs between root and final path, "
"enabling CIFS_MOUNT_USE_PREFIX_PATH\n"); "enabling CIFS_MOUNT_USE_PREFIX_PATH\n");
cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH; cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
rc = 0; rc = 0;
}
} }
kfree(full_path); kfree(full_path);
} }
......
...@@ -305,6 +305,7 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file, ...@@ -305,6 +305,7 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file,
cfile->tlink = cifs_get_tlink(tlink); cfile->tlink = cifs_get_tlink(tlink);
INIT_WORK(&cfile->oplock_break, cifs_oplock_break); INIT_WORK(&cfile->oplock_break, cifs_oplock_break);
mutex_init(&cfile->fh_mutex); mutex_init(&cfile->fh_mutex);
spin_lock_init(&cfile->file_info_lock);
cifs_sb_active(inode->i_sb); cifs_sb_active(inode->i_sb);
...@@ -317,7 +318,7 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file, ...@@ -317,7 +318,7 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file,
oplock = 0; oplock = 0;
} }
spin_lock(&cifs_file_list_lock); spin_lock(&tcon->open_file_lock);
if (fid->pending_open->oplock != CIFS_OPLOCK_NO_CHANGE && oplock) if (fid->pending_open->oplock != CIFS_OPLOCK_NO_CHANGE && oplock)
oplock = fid->pending_open->oplock; oplock = fid->pending_open->oplock;
list_del(&fid->pending_open->olist); list_del(&fid->pending_open->olist);
...@@ -326,12 +327,13 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file, ...@@ -326,12 +327,13 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file,
server->ops->set_fid(cfile, fid, oplock); server->ops->set_fid(cfile, fid, oplock);
list_add(&cfile->tlist, &tcon->openFileList); list_add(&cfile->tlist, &tcon->openFileList);
/* if readable file instance put first in list*/ /* if readable file instance put first in list*/
if (file->f_mode & FMODE_READ) if (file->f_mode & FMODE_READ)
list_add(&cfile->flist, &cinode->openFileList); list_add(&cfile->flist, &cinode->openFileList);
else else
list_add_tail(&cfile->flist, &cinode->openFileList); list_add_tail(&cfile->flist, &cinode->openFileList);
spin_unlock(&cifs_file_list_lock); spin_unlock(&tcon->open_file_lock);
if (fid->purge_cache) if (fid->purge_cache)
cifs_zap_mapping(inode); cifs_zap_mapping(inode);
...@@ -343,16 +345,16 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file, ...@@ -343,16 +345,16 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file,
struct cifsFileInfo * struct cifsFileInfo *
cifsFileInfo_get(struct cifsFileInfo *cifs_file) cifsFileInfo_get(struct cifsFileInfo *cifs_file)
{ {
spin_lock(&cifs_file_list_lock); spin_lock(&cifs_file->file_info_lock);
cifsFileInfo_get_locked(cifs_file); cifsFileInfo_get_locked(cifs_file);
spin_unlock(&cifs_file_list_lock); spin_unlock(&cifs_file->file_info_lock);
return cifs_file; return cifs_file;
} }
/* /*
* Release a reference on the file private data. This may involve closing * Release a reference on the file private data. This may involve closing
* the filehandle out on the server. Must be called without holding * the filehandle out on the server. Must be called without holding
* cifs_file_list_lock. * tcon->open_file_lock and cifs_file->file_info_lock.
*/ */
void cifsFileInfo_put(struct cifsFileInfo *cifs_file) void cifsFileInfo_put(struct cifsFileInfo *cifs_file)
{ {
...@@ -367,11 +369,15 @@ void cifsFileInfo_put(struct cifsFileInfo *cifs_file) ...@@ -367,11 +369,15 @@ void cifsFileInfo_put(struct cifsFileInfo *cifs_file)
struct cifs_pending_open open; struct cifs_pending_open open;
bool oplock_break_cancelled; bool oplock_break_cancelled;
spin_lock(&cifs_file_list_lock); spin_lock(&tcon->open_file_lock);
spin_lock(&cifs_file->file_info_lock);
if (--cifs_file->count > 0) { if (--cifs_file->count > 0) {
spin_unlock(&cifs_file_list_lock); spin_unlock(&cifs_file->file_info_lock);
spin_unlock(&tcon->open_file_lock);
return; return;
} }
spin_unlock(&cifs_file->file_info_lock);
if (server->ops->get_lease_key) if (server->ops->get_lease_key)
server->ops->get_lease_key(inode, &fid); server->ops->get_lease_key(inode, &fid);
...@@ -395,7 +401,8 @@ void cifsFileInfo_put(struct cifsFileInfo *cifs_file) ...@@ -395,7 +401,8 @@ void cifsFileInfo_put(struct cifsFileInfo *cifs_file)
set_bit(CIFS_INO_INVALID_MAPPING, &cifsi->flags); set_bit(CIFS_INO_INVALID_MAPPING, &cifsi->flags);
cifs_set_oplock_level(cifsi, 0); cifs_set_oplock_level(cifsi, 0);
} }
spin_unlock(&cifs_file_list_lock);
spin_unlock(&tcon->open_file_lock);
oplock_break_cancelled = cancel_work_sync(&cifs_file->oplock_break); oplock_break_cancelled = cancel_work_sync(&cifs_file->oplock_break);
...@@ -732,6 +739,15 @@ cifs_reopen_file(struct cifsFileInfo *cfile, bool can_flush) ...@@ -732,6 +739,15 @@ cifs_reopen_file(struct cifsFileInfo *cfile, bool can_flush)
* to the server to get the new inode info. * to the server to get the new inode info.
*/ */
/*
* If the server returned a read oplock and we have mandatory brlocks,
* set oplock level to None.
*/
if (server->ops->is_read_op(oplock) && cifs_has_mand_locks(cinode)) {
cifs_dbg(FYI, "Reset oplock val from read to None due to mand locks\n");
oplock = 0;
}
server->ops->set_fid(cfile, &cfile->fid, oplock); server->ops->set_fid(cfile, &cfile->fid, oplock);
if (oparms.reconnect) if (oparms.reconnect)
cifs_relock_file(cfile); cifs_relock_file(cfile);
...@@ -753,6 +769,36 @@ int cifs_close(struct inode *inode, struct file *file) ...@@ -753,6 +769,36 @@ int cifs_close(struct inode *inode, struct file *file)
return 0; return 0;
} }
void
cifs_reopen_persistent_handles(struct cifs_tcon *tcon)
{
struct cifsFileInfo *open_file;
struct list_head *tmp;
struct list_head *tmp1;
struct list_head tmp_list;
cifs_dbg(FYI, "Reopen persistent handles");
INIT_LIST_HEAD(&tmp_list);
/* list all files open on tree connection, reopen resilient handles */
spin_lock(&tcon->open_file_lock);
list_for_each(tmp, &tcon->openFileList) {
open_file = list_entry(tmp, struct cifsFileInfo, tlist);
if (!open_file->invalidHandle)
continue;
cifsFileInfo_get(open_file);
list_add_tail(&open_file->rlist, &tmp_list);
}
spin_unlock(&tcon->open_file_lock);
list_for_each_safe(tmp, tmp1, &tmp_list) {
open_file = list_entry(tmp, struct cifsFileInfo, rlist);
cifs_reopen_file(open_file, false /* do not flush */);
list_del_init(&open_file->rlist);
cifsFileInfo_put(open_file);
}
}
int cifs_closedir(struct inode *inode, struct file *file) int cifs_closedir(struct inode *inode, struct file *file)
{ {
int rc = 0; int rc = 0;
...@@ -772,10 +818,10 @@ int cifs_closedir(struct inode *inode, struct file *file) ...@@ -772,10 +818,10 @@ int cifs_closedir(struct inode *inode, struct file *file)
server = tcon->ses->server; server = tcon->ses->server;
cifs_dbg(FYI, "Freeing private data in close dir\n"); cifs_dbg(FYI, "Freeing private data in close dir\n");
spin_lock(&cifs_file_list_lock); spin_lock(&cfile->file_info_lock);
if (server->ops->dir_needs_close(cfile)) { if (server->ops->dir_needs_close(cfile)) {
cfile->invalidHandle = true; cfile->invalidHandle = true;
spin_unlock(&cifs_file_list_lock); spin_unlock(&cfile->file_info_lock);
if (server->ops->close_dir) if (server->ops->close_dir)
rc = server->ops->close_dir(xid, tcon, &cfile->fid); rc = server->ops->close_dir(xid, tcon, &cfile->fid);
else else
...@@ -784,7 +830,7 @@ int cifs_closedir(struct inode *inode, struct file *file) ...@@ -784,7 +830,7 @@ int cifs_closedir(struct inode *inode, struct file *file)
/* not much we can do if it fails anyway, ignore rc */ /* not much we can do if it fails anyway, ignore rc */
rc = 0; rc = 0;
} else } else
spin_unlock(&cifs_file_list_lock); spin_unlock(&cfile->file_info_lock);
buf = cfile->srch_inf.ntwrk_buf_start; buf = cfile->srch_inf.ntwrk_buf_start;
if (buf) { if (buf) {
...@@ -1728,12 +1774,13 @@ struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *cifs_inode, ...@@ -1728,12 +1774,13 @@ struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *cifs_inode,
{ {
struct cifsFileInfo *open_file = NULL; struct cifsFileInfo *open_file = NULL;
struct cifs_sb_info *cifs_sb = CIFS_SB(cifs_inode->vfs_inode.i_sb); struct cifs_sb_info *cifs_sb = CIFS_SB(cifs_inode->vfs_inode.i_sb);
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
/* only filter by fsuid on multiuser mounts */ /* only filter by fsuid on multiuser mounts */
if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MULTIUSER)) if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MULTIUSER))
fsuid_only = false; fsuid_only = false;
spin_lock(&cifs_file_list_lock); spin_lock(&tcon->open_file_lock);
/* we could simply get the first_list_entry since write-only entries /* we could simply get the first_list_entry since write-only entries
are always at the end of the list but since the first entry might are always at the end of the list but since the first entry might
have a close pending, we go through the whole list */ have a close pending, we go through the whole list */
...@@ -1744,8 +1791,8 @@ struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *cifs_inode, ...@@ -1744,8 +1791,8 @@ struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *cifs_inode,
if (!open_file->invalidHandle) { if (!open_file->invalidHandle) {
/* found a good file */ /* found a good file */
/* lock it so it will not be closed on us */ /* lock it so it will not be closed on us */
cifsFileInfo_get_locked(open_file); cifsFileInfo_get(open_file);
spin_unlock(&cifs_file_list_lock); spin_unlock(&tcon->open_file_lock);
return open_file; return open_file;
} /* else might as well continue, and look for } /* else might as well continue, and look for
another, or simply have the caller reopen it another, or simply have the caller reopen it
...@@ -1753,7 +1800,7 @@ struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *cifs_inode, ...@@ -1753,7 +1800,7 @@ struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *cifs_inode,
} else /* write only file */ } else /* write only file */
break; /* write only files are last so must be done */ break; /* write only files are last so must be done */
} }
spin_unlock(&cifs_file_list_lock); spin_unlock(&tcon->open_file_lock);
return NULL; return NULL;
} }
...@@ -1762,6 +1809,7 @@ struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *cifs_inode, ...@@ -1762,6 +1809,7 @@ struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *cifs_inode,
{ {
struct cifsFileInfo *open_file, *inv_file = NULL; struct cifsFileInfo *open_file, *inv_file = NULL;
struct cifs_sb_info *cifs_sb; struct cifs_sb_info *cifs_sb;
struct cifs_tcon *tcon;
bool any_available = false; bool any_available = false;
int rc; int rc;
unsigned int refind = 0; unsigned int refind = 0;
...@@ -1777,15 +1825,16 @@ struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *cifs_inode, ...@@ -1777,15 +1825,16 @@ struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *cifs_inode,
} }
cifs_sb = CIFS_SB(cifs_inode->vfs_inode.i_sb); cifs_sb = CIFS_SB(cifs_inode->vfs_inode.i_sb);
tcon = cifs_sb_master_tcon(cifs_sb);
/* only filter by fsuid on multiuser mounts */ /* only filter by fsuid on multiuser mounts */
if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MULTIUSER)) if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MULTIUSER))
fsuid_only = false; fsuid_only = false;
spin_lock(&cifs_file_list_lock); spin_lock(&tcon->open_file_lock);
refind_writable: refind_writable:
if (refind > MAX_REOPEN_ATT) { if (refind > MAX_REOPEN_ATT) {
spin_unlock(&cifs_file_list_lock); spin_unlock(&tcon->open_file_lock);
return NULL; return NULL;
} }
list_for_each_entry(open_file, &cifs_inode->openFileList, flist) { list_for_each_entry(open_file, &cifs_inode->openFileList, flist) {
...@@ -1796,8 +1845,8 @@ struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *cifs_inode, ...@@ -1796,8 +1845,8 @@ struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *cifs_inode,
if (OPEN_FMODE(open_file->f_flags) & FMODE_WRITE) { if (OPEN_FMODE(open_file->f_flags) & FMODE_WRITE) {
if (!open_file->invalidHandle) { if (!open_file->invalidHandle) {
/* found a good writable file */ /* found a good writable file */
cifsFileInfo_get_locked(open_file); cifsFileInfo_get(open_file);
spin_unlock(&cifs_file_list_lock); spin_unlock(&tcon->open_file_lock);
return open_file; return open_file;
} else { } else {
if (!inv_file) if (!inv_file)
...@@ -1813,24 +1862,24 @@ struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *cifs_inode, ...@@ -1813,24 +1862,24 @@ struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *cifs_inode,
if (inv_file) { if (inv_file) {
any_available = false; any_available = false;
cifsFileInfo_get_locked(inv_file); cifsFileInfo_get(inv_file);
} }
spin_unlock(&cifs_file_list_lock); spin_unlock(&tcon->open_file_lock);
if (inv_file) { if (inv_file) {
rc = cifs_reopen_file(inv_file, false); rc = cifs_reopen_file(inv_file, false);
if (!rc) if (!rc)
return inv_file; return inv_file;
else { else {
spin_lock(&cifs_file_list_lock); spin_lock(&tcon->open_file_lock);
list_move_tail(&inv_file->flist, list_move_tail(&inv_file->flist,
&cifs_inode->openFileList); &cifs_inode->openFileList);
spin_unlock(&cifs_file_list_lock); spin_unlock(&tcon->open_file_lock);
cifsFileInfo_put(inv_file); cifsFileInfo_put(inv_file);
spin_lock(&cifs_file_list_lock);
++refind; ++refind;
inv_file = NULL; inv_file = NULL;
spin_lock(&tcon->open_file_lock);
goto refind_writable; goto refind_writable;
} }
} }
...@@ -3612,15 +3661,17 @@ static int cifs_readpage(struct file *file, struct page *page) ...@@ -3612,15 +3661,17 @@ static int cifs_readpage(struct file *file, struct page *page)
static int is_inode_writable(struct cifsInodeInfo *cifs_inode) static int is_inode_writable(struct cifsInodeInfo *cifs_inode)
{ {
struct cifsFileInfo *open_file; struct cifsFileInfo *open_file;
struct cifs_tcon *tcon =
cifs_sb_master_tcon(CIFS_SB(cifs_inode->vfs_inode.i_sb));
spin_lock(&cifs_file_list_lock); spin_lock(&tcon->open_file_lock);
list_for_each_entry(open_file, &cifs_inode->openFileList, flist) { list_for_each_entry(open_file, &cifs_inode->openFileList, flist) {
if (OPEN_FMODE(open_file->f_flags) & FMODE_WRITE) { if (OPEN_FMODE(open_file->f_flags) & FMODE_WRITE) {
spin_unlock(&cifs_file_list_lock); spin_unlock(&tcon->open_file_lock);
return 1; return 1;
} }
} }
spin_unlock(&cifs_file_list_lock); spin_unlock(&tcon->open_file_lock);
return 0; return 0;
} }
......
...@@ -189,7 +189,7 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg) ...@@ -189,7 +189,7 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg)
xid = get_xid(); xid = get_xid();
cifs_sb = CIFS_SB(inode->i_sb); cifs_sb = CIFS_SB(inode->i_sb);
cifs_dbg(VFS, "cifs ioctl 0x%x\n", command);
switch (command) { switch (command) {
case FS_IOC_GETFLAGS: case FS_IOC_GETFLAGS:
if (pSMBFile == NULL) if (pSMBFile == NULL)
...@@ -267,11 +267,23 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg) ...@@ -267,11 +267,23 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg)
tcon = tlink_tcon(pSMBFile->tlink); tcon = tlink_tcon(pSMBFile->tlink);
rc = smb_mnt_get_fsinfo(xid, tcon, (void __user *)arg); rc = smb_mnt_get_fsinfo(xid, tcon, (void __user *)arg);
break; break;
case CIFS_ENUMERATE_SNAPSHOTS:
if (arg == 0) {
rc = -EINVAL;
goto cifs_ioc_exit;
}
tcon = tlink_tcon(pSMBFile->tlink);
if (tcon->ses->server->ops->enum_snapshots)
rc = tcon->ses->server->ops->enum_snapshots(xid, tcon,
pSMBFile, (void __user *)arg);
else
rc = -EOPNOTSUPP;
break;
default: default:
cifs_dbg(FYI, "unsupported ioctl\n"); cifs_dbg(FYI, "unsupported ioctl\n");
break; break;
} }
cifs_ioc_exit:
free_xid(xid); free_xid(xid);
return rc; return rc;
} }
...@@ -120,6 +120,7 @@ tconInfoAlloc(void) ...@@ -120,6 +120,7 @@ tconInfoAlloc(void)
++ret_buf->tc_count; ++ret_buf->tc_count;
INIT_LIST_HEAD(&ret_buf->openFileList); INIT_LIST_HEAD(&ret_buf->openFileList);
INIT_LIST_HEAD(&ret_buf->tcon_list); INIT_LIST_HEAD(&ret_buf->tcon_list);
spin_lock_init(&ret_buf->open_file_lock);
#ifdef CONFIG_CIFS_STATS #ifdef CONFIG_CIFS_STATS
spin_lock_init(&ret_buf->stat_lock); spin_lock_init(&ret_buf->stat_lock);
#endif #endif
...@@ -465,7 +466,7 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv) ...@@ -465,7 +466,7 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv)
continue; continue;
cifs_stats_inc(&tcon->stats.cifs_stats.num_oplock_brks); cifs_stats_inc(&tcon->stats.cifs_stats.num_oplock_brks);
spin_lock(&cifs_file_list_lock); spin_lock(&tcon->open_file_lock);
list_for_each(tmp2, &tcon->openFileList) { list_for_each(tmp2, &tcon->openFileList) {
netfile = list_entry(tmp2, struct cifsFileInfo, netfile = list_entry(tmp2, struct cifsFileInfo,
tlist); tlist);
...@@ -495,11 +496,11 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv) ...@@ -495,11 +496,11 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv)
&netfile->oplock_break); &netfile->oplock_break);
netfile->oplock_break_cancelled = false; netfile->oplock_break_cancelled = false;
spin_unlock(&cifs_file_list_lock); spin_unlock(&tcon->open_file_lock);
spin_unlock(&cifs_tcp_ses_lock); spin_unlock(&cifs_tcp_ses_lock);
return true; return true;
} }
spin_unlock(&cifs_file_list_lock); spin_unlock(&tcon->open_file_lock);
spin_unlock(&cifs_tcp_ses_lock); spin_unlock(&cifs_tcp_ses_lock);
cifs_dbg(FYI, "No matching file for oplock break\n"); cifs_dbg(FYI, "No matching file for oplock break\n");
return true; return true;
...@@ -613,9 +614,9 @@ backup_cred(struct cifs_sb_info *cifs_sb) ...@@ -613,9 +614,9 @@ backup_cred(struct cifs_sb_info *cifs_sb)
void void
cifs_del_pending_open(struct cifs_pending_open *open) cifs_del_pending_open(struct cifs_pending_open *open)
{ {
spin_lock(&cifs_file_list_lock); spin_lock(&tlink_tcon(open->tlink)->open_file_lock);
list_del(&open->olist); list_del(&open->olist);
spin_unlock(&cifs_file_list_lock); spin_unlock(&tlink_tcon(open->tlink)->open_file_lock);
} }
void void
...@@ -635,7 +636,7 @@ void ...@@ -635,7 +636,7 @@ void
cifs_add_pending_open(struct cifs_fid *fid, struct tcon_link *tlink, cifs_add_pending_open(struct cifs_fid *fid, struct tcon_link *tlink,
struct cifs_pending_open *open) struct cifs_pending_open *open)
{ {
spin_lock(&cifs_file_list_lock); spin_lock(&tlink_tcon(tlink)->open_file_lock);
cifs_add_pending_open_locked(fid, tlink, open); cifs_add_pending_open_locked(fid, tlink, open);
spin_unlock(&cifs_file_list_lock); spin_unlock(&tlink_tcon(open->tlink)->open_file_lock);
} }
...@@ -597,14 +597,14 @@ find_cifs_entry(const unsigned int xid, struct cifs_tcon *tcon, loff_t pos, ...@@ -597,14 +597,14 @@ find_cifs_entry(const unsigned int xid, struct cifs_tcon *tcon, loff_t pos,
is_dir_changed(file)) || (index_to_find < first_entry_in_buffer)) { is_dir_changed(file)) || (index_to_find < first_entry_in_buffer)) {
/* close and restart search */ /* close and restart search */
cifs_dbg(FYI, "search backing up - close and restart search\n"); cifs_dbg(FYI, "search backing up - close and restart search\n");
spin_lock(&cifs_file_list_lock); spin_lock(&cfile->file_info_lock);
if (server->ops->dir_needs_close(cfile)) { if (server->ops->dir_needs_close(cfile)) {
cfile->invalidHandle = true; cfile->invalidHandle = true;
spin_unlock(&cifs_file_list_lock); spin_unlock(&cfile->file_info_lock);
if (server->ops->close_dir) if (server->ops->close_dir)
server->ops->close_dir(xid, tcon, &cfile->fid); server->ops->close_dir(xid, tcon, &cfile->fid);
} else } else
spin_unlock(&cifs_file_list_lock); spin_unlock(&cfile->file_info_lock);
if (cfile->srch_inf.ntwrk_buf_start) { if (cfile->srch_inf.ntwrk_buf_start) {
cifs_dbg(FYI, "freeing SMB ff cache buf on search rewind\n"); cifs_dbg(FYI, "freeing SMB ff cache buf on search rewind\n");
if (cfile->srch_inf.smallBuf) if (cfile->srch_inf.smallBuf)
......
...@@ -266,9 +266,15 @@ smb2_set_file_info(struct inode *inode, const char *full_path, ...@@ -266,9 +266,15 @@ smb2_set_file_info(struct inode *inode, const char *full_path,
struct tcon_link *tlink; struct tcon_link *tlink;
int rc; int rc;
if ((buf->CreationTime == 0) && (buf->LastAccessTime == 0) &&
(buf->LastWriteTime == 0) && (buf->ChangeTime) &&
(buf->Attributes == 0))
return 0; /* would be a no op, no sense sending this */
tlink = cifs_sb_tlink(cifs_sb); tlink = cifs_sb_tlink(cifs_sb);
if (IS_ERR(tlink)) if (IS_ERR(tlink))
return PTR_ERR(tlink); return PTR_ERR(tlink);
rc = smb2_open_op_close(xid, tlink_tcon(tlink), cifs_sb, full_path, rc = smb2_open_op_close(xid, tlink_tcon(tlink), cifs_sb, full_path,
FILE_WRITE_ATTRIBUTES, FILE_OPEN, 0, buf, FILE_WRITE_ATTRIBUTES, FILE_OPEN, 0, buf,
SMB2_OP_SET_INFO); SMB2_OP_SET_INFO);
......
...@@ -549,19 +549,19 @@ smb2_is_valid_lease_break(char *buffer) ...@@ -549,19 +549,19 @@ smb2_is_valid_lease_break(char *buffer)
list_for_each(tmp1, &server->smb_ses_list) { list_for_each(tmp1, &server->smb_ses_list) {
ses = list_entry(tmp1, struct cifs_ses, smb_ses_list); ses = list_entry(tmp1, struct cifs_ses, smb_ses_list);
spin_lock(&cifs_file_list_lock);
list_for_each(tmp2, &ses->tcon_list) { list_for_each(tmp2, &ses->tcon_list) {
tcon = list_entry(tmp2, struct cifs_tcon, tcon = list_entry(tmp2, struct cifs_tcon,
tcon_list); tcon_list);
spin_lock(&tcon->open_file_lock);
cifs_stats_inc( cifs_stats_inc(
&tcon->stats.cifs_stats.num_oplock_brks); &tcon->stats.cifs_stats.num_oplock_brks);
if (smb2_tcon_has_lease(tcon, rsp, lw)) { if (smb2_tcon_has_lease(tcon, rsp, lw)) {
spin_unlock(&cifs_file_list_lock); spin_unlock(&tcon->open_file_lock);
spin_unlock(&cifs_tcp_ses_lock); spin_unlock(&cifs_tcp_ses_lock);
return true; return true;
} }
spin_unlock(&tcon->open_file_lock);
} }
spin_unlock(&cifs_file_list_lock);
} }
} }
spin_unlock(&cifs_tcp_ses_lock); spin_unlock(&cifs_tcp_ses_lock);
...@@ -603,7 +603,7 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server) ...@@ -603,7 +603,7 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server)
tcon = list_entry(tmp1, struct cifs_tcon, tcon_list); tcon = list_entry(tmp1, struct cifs_tcon, tcon_list);
cifs_stats_inc(&tcon->stats.cifs_stats.num_oplock_brks); cifs_stats_inc(&tcon->stats.cifs_stats.num_oplock_brks);
spin_lock(&cifs_file_list_lock); spin_lock(&tcon->open_file_lock);
list_for_each(tmp2, &tcon->openFileList) { list_for_each(tmp2, &tcon->openFileList) {
cfile = list_entry(tmp2, struct cifsFileInfo, cfile = list_entry(tmp2, struct cifsFileInfo,
tlist); tlist);
...@@ -615,7 +615,7 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server) ...@@ -615,7 +615,7 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server)
cifs_dbg(FYI, "file id match, oplock break\n"); cifs_dbg(FYI, "file id match, oplock break\n");
cinode = CIFS_I(d_inode(cfile->dentry)); cinode = CIFS_I(d_inode(cfile->dentry));
spin_lock(&cfile->file_info_lock);
if (!CIFS_CACHE_WRITE(cinode) && if (!CIFS_CACHE_WRITE(cinode) &&
rsp->OplockLevel == SMB2_OPLOCK_LEVEL_NONE) rsp->OplockLevel == SMB2_OPLOCK_LEVEL_NONE)
cfile->oplock_break_cancelled = true; cfile->oplock_break_cancelled = true;
...@@ -637,14 +637,14 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server) ...@@ -637,14 +637,14 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server)
clear_bit( clear_bit(
CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2, CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
&cinode->flags); &cinode->flags);
spin_unlock(&cfile->file_info_lock);
queue_work(cifsiod_wq, &cfile->oplock_break); queue_work(cifsiod_wq, &cfile->oplock_break);
spin_unlock(&cifs_file_list_lock); spin_unlock(&tcon->open_file_lock);
spin_unlock(&cifs_tcp_ses_lock); spin_unlock(&cifs_tcp_ses_lock);
return true; return true;
} }
spin_unlock(&cifs_file_list_lock); spin_unlock(&tcon->open_file_lock);
spin_unlock(&cifs_tcp_ses_lock); spin_unlock(&cifs_tcp_ses_lock);
cifs_dbg(FYI, "No matching file for oplock break\n"); cifs_dbg(FYI, "No matching file for oplock break\n");
return true; return true;
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
#include "cifs_unicode.h" #include "cifs_unicode.h"
#include "smb2status.h" #include "smb2status.h"
#include "smb2glob.h" #include "smb2glob.h"
#include "cifs_ioctl.h"
static int static int
change_conf(struct TCP_Server_Info *server) change_conf(struct TCP_Server_Info *server)
...@@ -70,6 +71,10 @@ smb2_add_credits(struct TCP_Server_Info *server, const unsigned int add, ...@@ -70,6 +71,10 @@ smb2_add_credits(struct TCP_Server_Info *server, const unsigned int add,
spin_lock(&server->req_lock); spin_lock(&server->req_lock);
val = server->ops->get_credits_field(server, optype); val = server->ops->get_credits_field(server, optype);
*val += add; *val += add;
if (*val > 65000) {
*val = 65000; /* Don't get near 64K credits, avoid srv bugs */
printk_once(KERN_WARNING "server overflowed SMB3 credits\n");
}
server->in_flight--; server->in_flight--;
if (server->in_flight == 0 && (optype & CIFS_OP_MASK) != CIFS_NEG_OP) if (server->in_flight == 0 && (optype & CIFS_OP_MASK) != CIFS_NEG_OP)
rc = change_conf(server); rc = change_conf(server);
...@@ -287,7 +292,7 @@ SMB3_request_interfaces(const unsigned int xid, struct cifs_tcon *tcon) ...@@ -287,7 +292,7 @@ SMB3_request_interfaces(const unsigned int xid, struct cifs_tcon *tcon)
cifs_dbg(FYI, "Link Speed %lld\n", cifs_dbg(FYI, "Link Speed %lld\n",
le64_to_cpu(out_buf->LinkSpeed)); le64_to_cpu(out_buf->LinkSpeed));
} }
kfree(out_buf);
return rc; return rc;
} }
#endif /* STATS2 */ #endif /* STATS2 */
...@@ -541,6 +546,7 @@ smb2_set_fid(struct cifsFileInfo *cfile, struct cifs_fid *fid, __u32 oplock) ...@@ -541,6 +546,7 @@ smb2_set_fid(struct cifsFileInfo *cfile, struct cifs_fid *fid, __u32 oplock)
server->ops->set_oplock_level(cinode, oplock, fid->epoch, server->ops->set_oplock_level(cinode, oplock, fid->epoch,
&fid->purge_cache); &fid->purge_cache);
cinode->can_cache_brlcks = CIFS_CACHE_WRITE(cinode); cinode->can_cache_brlcks = CIFS_CACHE_WRITE(cinode);
memcpy(cfile->fid.create_guid, fid->create_guid, 16);
} }
static void static void
...@@ -699,6 +705,7 @@ smb2_clone_range(const unsigned int xid, ...@@ -699,6 +705,7 @@ smb2_clone_range(const unsigned int xid,
cchunk_out: cchunk_out:
kfree(pcchunk); kfree(pcchunk);
kfree(retbuf);
return rc; return rc;
} }
...@@ -823,7 +830,6 @@ smb2_duplicate_extents(const unsigned int xid, ...@@ -823,7 +830,6 @@ smb2_duplicate_extents(const unsigned int xid,
{ {
int rc; int rc;
unsigned int ret_data_len; unsigned int ret_data_len;
char *retbuf = NULL;
struct duplicate_extents_to_file dup_ext_buf; struct duplicate_extents_to_file dup_ext_buf;
struct cifs_tcon *tcon = tlink_tcon(trgtfile->tlink); struct cifs_tcon *tcon = tlink_tcon(trgtfile->tlink);
...@@ -849,7 +855,7 @@ smb2_duplicate_extents(const unsigned int xid, ...@@ -849,7 +855,7 @@ smb2_duplicate_extents(const unsigned int xid,
FSCTL_DUPLICATE_EXTENTS_TO_FILE, FSCTL_DUPLICATE_EXTENTS_TO_FILE,
true /* is_fsctl */, (char *)&dup_ext_buf, true /* is_fsctl */, (char *)&dup_ext_buf,
sizeof(struct duplicate_extents_to_file), sizeof(struct duplicate_extents_to_file),
(char **)&retbuf, NULL,
&ret_data_len); &ret_data_len);
if (ret_data_len > 0) if (ret_data_len > 0)
...@@ -872,7 +878,6 @@ smb3_set_integrity(const unsigned int xid, struct cifs_tcon *tcon, ...@@ -872,7 +878,6 @@ smb3_set_integrity(const unsigned int xid, struct cifs_tcon *tcon,
struct cifsFileInfo *cfile) struct cifsFileInfo *cfile)
{ {
struct fsctl_set_integrity_information_req integr_info; struct fsctl_set_integrity_information_req integr_info;
char *retbuf = NULL;
unsigned int ret_data_len; unsigned int ret_data_len;
integr_info.ChecksumAlgorithm = cpu_to_le16(CHECKSUM_TYPE_UNCHANGED); integr_info.ChecksumAlgorithm = cpu_to_le16(CHECKSUM_TYPE_UNCHANGED);
...@@ -884,9 +889,53 @@ smb3_set_integrity(const unsigned int xid, struct cifs_tcon *tcon, ...@@ -884,9 +889,53 @@ smb3_set_integrity(const unsigned int xid, struct cifs_tcon *tcon,
FSCTL_SET_INTEGRITY_INFORMATION, FSCTL_SET_INTEGRITY_INFORMATION,
true /* is_fsctl */, (char *)&integr_info, true /* is_fsctl */, (char *)&integr_info,
sizeof(struct fsctl_set_integrity_information_req), sizeof(struct fsctl_set_integrity_information_req),
NULL,
&ret_data_len);
}
static int
smb3_enum_snapshots(const unsigned int xid, struct cifs_tcon *tcon,
struct cifsFileInfo *cfile, void __user *ioc_buf)
{
char *retbuf = NULL;
unsigned int ret_data_len = 0;
int rc;
struct smb_snapshot_array snapshot_in;
rc = SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid,
cfile->fid.volatile_fid,
FSCTL_SRV_ENUMERATE_SNAPSHOTS,
true /* is_fsctl */, NULL, 0 /* no input data */,
(char **)&retbuf, (char **)&retbuf,
&ret_data_len); &ret_data_len);
cifs_dbg(FYI, "enum snaphots ioctl returned %d and ret buflen is %d\n",
rc, ret_data_len);
if (rc)
return rc;
if (ret_data_len && (ioc_buf != NULL) && (retbuf != NULL)) {
/* Fixup buffer */
if (copy_from_user(&snapshot_in, ioc_buf,
sizeof(struct smb_snapshot_array))) {
rc = -EFAULT;
kfree(retbuf);
return rc;
}
if (snapshot_in.snapshot_array_size < sizeof(struct smb_snapshot_array)) {
rc = -ERANGE;
return rc;
}
if (ret_data_len > snapshot_in.snapshot_array_size)
ret_data_len = snapshot_in.snapshot_array_size;
if (copy_to_user(ioc_buf, retbuf, ret_data_len))
rc = -EFAULT;
}
kfree(retbuf);
return rc;
} }
static int static int
...@@ -1041,7 +1090,7 @@ smb2_set_lease_key(struct inode *inode, struct cifs_fid *fid) ...@@ -1041,7 +1090,7 @@ smb2_set_lease_key(struct inode *inode, struct cifs_fid *fid)
static void static void
smb2_new_lease_key(struct cifs_fid *fid) smb2_new_lease_key(struct cifs_fid *fid)
{ {
get_random_bytes(fid->lease_key, SMB2_LEASE_KEY_SIZE); generate_random_uuid(fid->lease_key);
} }
#define SMB2_SYMLINK_STRUCT_SIZE \ #define SMB2_SYMLINK_STRUCT_SIZE \
...@@ -1654,6 +1703,7 @@ struct smb_version_operations smb21_operations = { ...@@ -1654,6 +1703,7 @@ struct smb_version_operations smb21_operations = {
.clone_range = smb2_clone_range, .clone_range = smb2_clone_range,
.wp_retry_size = smb2_wp_retry_size, .wp_retry_size = smb2_wp_retry_size,
.dir_needs_close = smb2_dir_needs_close, .dir_needs_close = smb2_dir_needs_close,
.enum_snapshots = smb3_enum_snapshots,
}; };
struct smb_version_operations smb30_operations = { struct smb_version_operations smb30_operations = {
...@@ -1740,6 +1790,7 @@ struct smb_version_operations smb30_operations = { ...@@ -1740,6 +1790,7 @@ struct smb_version_operations smb30_operations = {
.wp_retry_size = smb2_wp_retry_size, .wp_retry_size = smb2_wp_retry_size,
.dir_needs_close = smb2_dir_needs_close, .dir_needs_close = smb2_dir_needs_close,
.fallocate = smb3_fallocate, .fallocate = smb3_fallocate,
.enum_snapshots = smb3_enum_snapshots,
}; };
#ifdef CONFIG_CIFS_SMB311 #ifdef CONFIG_CIFS_SMB311
...@@ -1827,6 +1878,7 @@ struct smb_version_operations smb311_operations = { ...@@ -1827,6 +1878,7 @@ struct smb_version_operations smb311_operations = {
.wp_retry_size = smb2_wp_retry_size, .wp_retry_size = smb2_wp_retry_size,
.dir_needs_close = smb2_dir_needs_close, .dir_needs_close = smb2_dir_needs_close,
.fallocate = smb3_fallocate, .fallocate = smb3_fallocate,
.enum_snapshots = smb3_enum_snapshots,
}; };
#endif /* CIFS_SMB311 */ #endif /* CIFS_SMB311 */
......
...@@ -100,7 +100,21 @@ smb2_hdr_assemble(struct smb2_hdr *hdr, __le16 smb2_cmd /* command */ , ...@@ -100,7 +100,21 @@ smb2_hdr_assemble(struct smb2_hdr *hdr, __le16 smb2_cmd /* command */ ,
hdr->ProtocolId = SMB2_PROTO_NUMBER; hdr->ProtocolId = SMB2_PROTO_NUMBER;
hdr->StructureSize = cpu_to_le16(64); hdr->StructureSize = cpu_to_le16(64);
hdr->Command = smb2_cmd; hdr->Command = smb2_cmd;
hdr->CreditRequest = cpu_to_le16(2); /* BB make this dynamic */ if (tcon && tcon->ses && tcon->ses->server) {
struct TCP_Server_Info *server = tcon->ses->server;
spin_lock(&server->req_lock);
/* Request up to 2 credits but don't go over the limit. */
if (server->credits >= server->max_credits)
hdr->CreditRequest = cpu_to_le16(0);
else
hdr->CreditRequest = cpu_to_le16(
min_t(int, server->max_credits -
server->credits, 2));
spin_unlock(&server->req_lock);
} else {
hdr->CreditRequest = cpu_to_le16(2);
}
hdr->ProcessId = cpu_to_le32((__u16)current->tgid); hdr->ProcessId = cpu_to_le32((__u16)current->tgid);
if (!tcon) if (!tcon)
...@@ -236,8 +250,13 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon) ...@@ -236,8 +250,13 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon)
} }
cifs_mark_open_files_invalid(tcon); cifs_mark_open_files_invalid(tcon);
rc = SMB2_tcon(0, tcon->ses, tcon->treeName, tcon, nls_codepage); rc = SMB2_tcon(0, tcon->ses, tcon->treeName, tcon, nls_codepage);
mutex_unlock(&tcon->ses->session_mutex); mutex_unlock(&tcon->ses->session_mutex);
if (tcon->use_persistent)
cifs_reopen_persistent_handles(tcon);
cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc); cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc);
if (rc) if (rc)
goto out; goto out;
...@@ -574,59 +593,42 @@ int smb3_validate_negotiate(const unsigned int xid, struct cifs_tcon *tcon) ...@@ -574,59 +593,42 @@ int smb3_validate_negotiate(const unsigned int xid, struct cifs_tcon *tcon)
return -EIO; return -EIO;
} }
int struct SMB2_sess_data {
SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, unsigned int xid;
const struct nls_table *nls_cp) struct cifs_ses *ses;
struct nls_table *nls_cp;
void (*func)(struct SMB2_sess_data *);
int result;
u64 previous_session;
/* we will send the SMB in three pieces:
* a fixed length beginning part, an optional
* SPNEGO blob (which can be zero length), and a
* last part which will include the strings
* and rest of bcc area. This allows us to avoid
* a large buffer 17K allocation
*/
int buf0_type;
struct kvec iov[2];
};
static int
SMB2_sess_alloc_buffer(struct SMB2_sess_data *sess_data)
{ {
int rc;
struct cifs_ses *ses = sess_data->ses;
struct smb2_sess_setup_req *req; struct smb2_sess_setup_req *req;
struct smb2_sess_setup_rsp *rsp = NULL;
struct kvec iov[2];
int rc = 0;
int resp_buftype = CIFS_NO_BUFFER;
__le32 phase = NtLmNegotiate; /* NTLMSSP, if needed, is multistage */
struct TCP_Server_Info *server = ses->server; struct TCP_Server_Info *server = ses->server;
u16 blob_length = 0;
struct key *spnego_key = NULL;
char *security_blob = NULL;
unsigned char *ntlmssp_blob = NULL;
bool use_spnego = false; /* else use raw ntlmssp */
cifs_dbg(FYI, "Session Setup\n");
if (!server) {
WARN(1, "%s: server is NULL!\n", __func__);
return -EIO;
}
/*
* If we are here due to reconnect, free per-smb session key
* in case signing was required.
*/
kfree(ses->auth_key.response);
ses->auth_key.response = NULL;
/*
* If memory allocation is successful, caller of this function
* frees it.
*/
ses->ntlmssp = kmalloc(sizeof(struct ntlmssp_auth), GFP_KERNEL);
if (!ses->ntlmssp)
return -ENOMEM;
ses->ntlmssp->sesskey_per_smbsess = true;
/* FIXME: allow for other auth types besides NTLMSSP (e.g. krb5) */
if (ses->sectype != Kerberos && ses->sectype != RawNTLMSSP)
ses->sectype = RawNTLMSSP;
ssetup_ntlmssp_authenticate:
if (phase == NtLmChallenge)
phase = NtLmAuthenticate; /* if ntlmssp, now final phase */
rc = small_smb2_init(SMB2_SESSION_SETUP, NULL, (void **) &req); rc = small_smb2_init(SMB2_SESSION_SETUP, NULL, (void **) &req);
if (rc) if (rc)
return rc; return rc;
req->hdr.SessionId = 0; /* First session, not a reauthenticate */ req->hdr.SessionId = 0; /* First session, not a reauthenticate */
/* if reconnect, we need to send previous sess id, otherwise it is 0 */
req->PreviousSessionId = sess_data->previous_session;
req->Flags = 0; /* MBZ */ req->Flags = 0; /* MBZ */
/* to enable echos and oplocks */ /* to enable echos and oplocks */
req->hdr.CreditRequest = cpu_to_le16(3); req->hdr.CreditRequest = cpu_to_le16(3);
...@@ -642,199 +644,368 @@ SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, ...@@ -642,199 +644,368 @@ SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses,
req->Capabilities = 0; req->Capabilities = 0;
req->Channel = 0; /* MBZ */ req->Channel = 0; /* MBZ */
iov[0].iov_base = (char *)req; sess_data->iov[0].iov_base = (char *)req;
/* 4 for rfc1002 length field and 1 for pad */ /* 4 for rfc1002 length field and 1 for pad */
iov[0].iov_len = get_rfc1002_length(req) + 4 - 1; sess_data->iov[0].iov_len = get_rfc1002_length(req) + 4 - 1;
/*
* This variable will be used to clear the buffer
* allocated above in case of any error in the calling function.
*/
sess_data->buf0_type = CIFS_SMALL_BUFFER;
if (ses->sectype == Kerberos) { return 0;
#ifdef CONFIG_CIFS_UPCALL }
struct cifs_spnego_msg *msg;
spnego_key = cifs_get_spnego_key(ses); static void
if (IS_ERR(spnego_key)) { SMB2_sess_free_buffer(struct SMB2_sess_data *sess_data)
rc = PTR_ERR(spnego_key); {
spnego_key = NULL; free_rsp_buf(sess_data->buf0_type, sess_data->iov[0].iov_base);
goto ssetup_exit; sess_data->buf0_type = CIFS_NO_BUFFER;
} }
msg = spnego_key->payload.data[0]; static int
/* SMB2_sess_sendreceive(struct SMB2_sess_data *sess_data)
* check version field to make sure that cifs.upcall is {
* sending us a response in an expected form int rc;
*/ struct smb2_sess_setup_req *req = sess_data->iov[0].iov_base;
if (msg->version != CIFS_SPNEGO_UPCALL_VERSION) {
cifs_dbg(VFS,
"bad cifs.upcall version. Expected %d got %d",
CIFS_SPNEGO_UPCALL_VERSION, msg->version);
rc = -EKEYREJECTED;
goto ssetup_exit;
}
ses->auth_key.response = kmemdup(msg->data, msg->sesskey_len,
GFP_KERNEL);
if (!ses->auth_key.response) {
cifs_dbg(VFS,
"Kerberos can't allocate (%u bytes) memory",
msg->sesskey_len);
rc = -ENOMEM;
goto ssetup_exit;
}
ses->auth_key.len = msg->sesskey_len;
blob_length = msg->secblob_len;
iov[1].iov_base = msg->data + msg->sesskey_len;
iov[1].iov_len = blob_length;
#else
rc = -EOPNOTSUPP;
goto ssetup_exit;
#endif /* CONFIG_CIFS_UPCALL */
} else if (phase == NtLmNegotiate) { /* if not krb5 must be ntlmssp */
ntlmssp_blob = kmalloc(sizeof(struct _NEGOTIATE_MESSAGE),
GFP_KERNEL);
if (ntlmssp_blob == NULL) {
rc = -ENOMEM;
goto ssetup_exit;
}
build_ntlmssp_negotiate_blob(ntlmssp_blob, ses);
if (use_spnego) {
/* blob_length = build_spnego_ntlmssp_blob(
&security_blob,
sizeof(struct _NEGOTIATE_MESSAGE),
ntlmssp_blob); */
/* BB eventually need to add this */
cifs_dbg(VFS, "spnego not supported for SMB2 yet\n");
rc = -EOPNOTSUPP;
kfree(ntlmssp_blob);
goto ssetup_exit;
} else {
blob_length = sizeof(struct _NEGOTIATE_MESSAGE);
/* with raw NTLMSSP we don't encapsulate in SPNEGO */
security_blob = ntlmssp_blob;
}
iov[1].iov_base = security_blob;
iov[1].iov_len = blob_length;
} else if (phase == NtLmAuthenticate) {
req->hdr.SessionId = ses->Suid;
rc = build_ntlmssp_auth_blob(&ntlmssp_blob, &blob_length, ses,
nls_cp);
if (rc) {
cifs_dbg(FYI, "build_ntlmssp_auth_blob failed %d\n",
rc);
goto ssetup_exit; /* BB double check error handling */
}
if (use_spnego) {
/* blob_length = build_spnego_ntlmssp_blob(
&security_blob,
blob_length,
ntlmssp_blob); */
cifs_dbg(VFS, "spnego not supported for SMB2 yet\n");
rc = -EOPNOTSUPP;
kfree(ntlmssp_blob);
goto ssetup_exit;
} else {
security_blob = ntlmssp_blob;
}
iov[1].iov_base = security_blob;
iov[1].iov_len = blob_length;
} else {
cifs_dbg(VFS, "illegal ntlmssp phase\n");
rc = -EIO;
goto ssetup_exit;
}
/* Testing shows that buffer offset must be at location of Buffer[0] */ /* Testing shows that buffer offset must be at location of Buffer[0] */
req->SecurityBufferOffset = req->SecurityBufferOffset =
cpu_to_le16(sizeof(struct smb2_sess_setup_req) - cpu_to_le16(sizeof(struct smb2_sess_setup_req) -
1 /* pad */ - 4 /* rfc1001 len */); 1 /* pad */ - 4 /* rfc1001 len */);
req->SecurityBufferLength = cpu_to_le16(blob_length); req->SecurityBufferLength = cpu_to_le16(sess_data->iov[1].iov_len);
inc_rfc1001_len(req, blob_length - 1 /* pad */); inc_rfc1001_len(req, sess_data->iov[1].iov_len - 1 /* pad */);
/* BB add code to build os and lm fields */ /* BB add code to build os and lm fields */
rc = SendReceive2(xid, ses, iov, 2, &resp_buftype, rc = SendReceive2(sess_data->xid, sess_data->ses,
CIFS_LOG_ERROR | CIFS_NEG_OP); sess_data->iov, 2,
&sess_data->buf0_type,
CIFS_LOG_ERROR | CIFS_NEG_OP);
kfree(security_blob); return rc;
rsp = (struct smb2_sess_setup_rsp *)iov[0].iov_base; }
ses->Suid = rsp->hdr.SessionId;
if (resp_buftype != CIFS_NO_BUFFER && static int
rsp->hdr.Status == STATUS_MORE_PROCESSING_REQUIRED) { SMB2_sess_establish_session(struct SMB2_sess_data *sess_data)
if (phase != NtLmNegotiate) { {
cifs_dbg(VFS, "Unexpected more processing error\n"); int rc = 0;
goto ssetup_exit; struct cifs_ses *ses = sess_data->ses;
}
if (offsetof(struct smb2_sess_setup_rsp, Buffer) - 4 != mutex_lock(&ses->server->srv_mutex);
le16_to_cpu(rsp->SecurityBufferOffset)) { if (ses->server->sign && ses->server->ops->generate_signingkey) {
cifs_dbg(VFS, "Invalid security buffer offset %d\n", rc = ses->server->ops->generate_signingkey(ses);
le16_to_cpu(rsp->SecurityBufferOffset)); kfree(ses->auth_key.response);
rc = -EIO; ses->auth_key.response = NULL;
goto ssetup_exit; if (rc) {
cifs_dbg(FYI,
"SMB3 session key generation failed\n");
mutex_unlock(&ses->server->srv_mutex);
goto keygen_exit;
} }
}
if (!ses->server->session_estab) {
ses->server->sequence_number = 0x2;
ses->server->session_estab = true;
}
mutex_unlock(&ses->server->srv_mutex);
cifs_dbg(FYI, "SMB2/3 session established successfully\n");
spin_lock(&GlobalMid_Lock);
ses->status = CifsGood;
ses->need_reconnect = false;
spin_unlock(&GlobalMid_Lock);
/* NTLMSSP Negotiate sent now processing challenge (response) */ keygen_exit:
phase = NtLmChallenge; /* process ntlmssp challenge */ if (!ses->server->sign) {
rc = 0; /* MORE_PROCESSING is not an error here but expected */ kfree(ses->auth_key.response);
rc = decode_ntlmssp_challenge(rsp->Buffer, ses->auth_key.response = NULL;
le16_to_cpu(rsp->SecurityBufferLength), ses); }
return rc;
}
#ifdef CONFIG_CIFS_UPCALL
static void
SMB2_auth_kerberos(struct SMB2_sess_data *sess_data)
{
int rc;
struct cifs_ses *ses = sess_data->ses;
struct cifs_spnego_msg *msg;
struct key *spnego_key = NULL;
struct smb2_sess_setup_rsp *rsp = NULL;
rc = SMB2_sess_alloc_buffer(sess_data);
if (rc)
goto out;
spnego_key = cifs_get_spnego_key(ses);
if (IS_ERR(spnego_key)) {
rc = PTR_ERR(spnego_key);
spnego_key = NULL;
goto out;
} }
msg = spnego_key->payload.data[0];
/* /*
* BB eventually add code for SPNEGO decoding of NtlmChallenge blob, * check version field to make sure that cifs.upcall is
* but at least the raw NTLMSSP case works. * sending us a response in an expected form
*/ */
if (msg->version != CIFS_SPNEGO_UPCALL_VERSION) {
cifs_dbg(VFS,
"bad cifs.upcall version. Expected %d got %d",
CIFS_SPNEGO_UPCALL_VERSION, msg->version);
rc = -EKEYREJECTED;
goto out_put_spnego_key;
}
ses->auth_key.response = kmemdup(msg->data, msg->sesskey_len,
GFP_KERNEL);
if (!ses->auth_key.response) {
cifs_dbg(VFS,
"Kerberos can't allocate (%u bytes) memory",
msg->sesskey_len);
rc = -ENOMEM;
goto out_put_spnego_key;
}
ses->auth_key.len = msg->sesskey_len;
sess_data->iov[1].iov_base = msg->data + msg->sesskey_len;
sess_data->iov[1].iov_len = msg->secblob_len;
rc = SMB2_sess_sendreceive(sess_data);
if (rc)
goto out_put_spnego_key;
rsp = (struct smb2_sess_setup_rsp *)sess_data->iov[0].iov_base;
ses->Suid = rsp->hdr.SessionId;
ses->session_flags = le16_to_cpu(rsp->SessionFlags);
if (ses->session_flags & SMB2_SESSION_FLAG_ENCRYPT_DATA)
cifs_dbg(VFS, "SMB3 encryption not supported yet\n");
rc = SMB2_sess_establish_session(sess_data);
out_put_spnego_key:
key_invalidate(spnego_key);
key_put(spnego_key);
out:
sess_data->result = rc;
sess_data->func = NULL;
SMB2_sess_free_buffer(sess_data);
}
#else
static void
SMB2_auth_kerberos(struct SMB2_sess_data *sess_data)
{
cifs_dbg(VFS, "Kerberos negotiated but upcall support disabled!\n");
sess_data->result = -EOPNOTSUPP;
sess_data->func = NULL;
}
#endif
static void
SMB2_sess_auth_rawntlmssp_authenticate(struct SMB2_sess_data *sess_data);
static void
SMB2_sess_auth_rawntlmssp_negotiate(struct SMB2_sess_data *sess_data)
{
int rc;
struct cifs_ses *ses = sess_data->ses;
struct smb2_sess_setup_rsp *rsp = NULL;
char *ntlmssp_blob = NULL;
bool use_spnego = false; /* else use raw ntlmssp */
u16 blob_length = 0;
/* /*
* No tcon so can't do * If memory allocation is successful, caller of this function
* cifs_stats_inc(&tcon->stats.smb2_stats.smb2_com_fail[SMB2...]); * frees it.
*/ */
if (rc != 0) ses->ntlmssp = kmalloc(sizeof(struct ntlmssp_auth), GFP_KERNEL);
goto ssetup_exit; if (!ses->ntlmssp) {
rc = -ENOMEM;
goto out_err;
}
ses->ntlmssp->sesskey_per_smbsess = true;
rc = SMB2_sess_alloc_buffer(sess_data);
if (rc)
goto out_err;
ntlmssp_blob = kmalloc(sizeof(struct _NEGOTIATE_MESSAGE),
GFP_KERNEL);
if (ntlmssp_blob == NULL) {
rc = -ENOMEM;
goto out;
}
build_ntlmssp_negotiate_blob(ntlmssp_blob, ses);
if (use_spnego) {
/* BB eventually need to add this */
cifs_dbg(VFS, "spnego not supported for SMB2 yet\n");
rc = -EOPNOTSUPP;
goto out;
} else {
blob_length = sizeof(struct _NEGOTIATE_MESSAGE);
/* with raw NTLMSSP we don't encapsulate in SPNEGO */
}
sess_data->iov[1].iov_base = ntlmssp_blob;
sess_data->iov[1].iov_len = blob_length;
rc = SMB2_sess_sendreceive(sess_data);
rsp = (struct smb2_sess_setup_rsp *)sess_data->iov[0].iov_base;
/* If true, rc here is expected and not an error */
if (sess_data->buf0_type != CIFS_NO_BUFFER &&
rsp->hdr.Status == STATUS_MORE_PROCESSING_REQUIRED)
rc = 0;
if (rc)
goto out;
if (offsetof(struct smb2_sess_setup_rsp, Buffer) - 4 !=
le16_to_cpu(rsp->SecurityBufferOffset)) {
cifs_dbg(VFS, "Invalid security buffer offset %d\n",
le16_to_cpu(rsp->SecurityBufferOffset));
rc = -EIO;
goto out;
}
rc = decode_ntlmssp_challenge(rsp->Buffer,
le16_to_cpu(rsp->SecurityBufferLength), ses);
if (rc)
goto out;
cifs_dbg(FYI, "rawntlmssp session setup challenge phase\n");
ses->Suid = rsp->hdr.SessionId;
ses->session_flags = le16_to_cpu(rsp->SessionFlags); ses->session_flags = le16_to_cpu(rsp->SessionFlags);
if (ses->session_flags & SMB2_SESSION_FLAG_ENCRYPT_DATA) if (ses->session_flags & SMB2_SESSION_FLAG_ENCRYPT_DATA)
cifs_dbg(VFS, "SMB3 encryption not supported yet\n"); cifs_dbg(VFS, "SMB3 encryption not supported yet\n");
ssetup_exit:
free_rsp_buf(resp_buftype, rsp);
/* if ntlmssp, and negotiate succeeded, proceed to authenticate phase */
if ((phase == NtLmChallenge) && (rc == 0))
goto ssetup_ntlmssp_authenticate;
out:
kfree(ntlmssp_blob);
SMB2_sess_free_buffer(sess_data);
if (!rc) { if (!rc) {
mutex_lock(&server->srv_mutex); sess_data->result = 0;
if (server->sign && server->ops->generate_signingkey) { sess_data->func = SMB2_sess_auth_rawntlmssp_authenticate;
rc = server->ops->generate_signingkey(ses); return;
kfree(ses->auth_key.response);
ses->auth_key.response = NULL;
if (rc) {
cifs_dbg(FYI,
"SMB3 session key generation failed\n");
mutex_unlock(&server->srv_mutex);
goto keygen_exit;
}
}
if (!server->session_estab) {
server->sequence_number = 0x2;
server->session_estab = true;
}
mutex_unlock(&server->srv_mutex);
cifs_dbg(FYI, "SMB2/3 session established successfully\n");
spin_lock(&GlobalMid_Lock);
ses->status = CifsGood;
ses->need_reconnect = false;
spin_unlock(&GlobalMid_Lock);
} }
out_err:
kfree(ses->ntlmssp);
ses->ntlmssp = NULL;
sess_data->result = rc;
sess_data->func = NULL;
}
keygen_exit: static void
if (!server->sign) { SMB2_sess_auth_rawntlmssp_authenticate(struct SMB2_sess_data *sess_data)
kfree(ses->auth_key.response); {
ses->auth_key.response = NULL; int rc;
struct cifs_ses *ses = sess_data->ses;
struct smb2_sess_setup_req *req;
struct smb2_sess_setup_rsp *rsp = NULL;
unsigned char *ntlmssp_blob = NULL;
bool use_spnego = false; /* else use raw ntlmssp */
u16 blob_length = 0;
rc = SMB2_sess_alloc_buffer(sess_data);
if (rc)
goto out;
req = (struct smb2_sess_setup_req *) sess_data->iov[0].iov_base;
req->hdr.SessionId = ses->Suid;
rc = build_ntlmssp_auth_blob(&ntlmssp_blob, &blob_length, ses,
sess_data->nls_cp);
if (rc) {
cifs_dbg(FYI, "build_ntlmssp_auth_blob failed %d\n", rc);
goto out;
} }
if (spnego_key) {
key_invalidate(spnego_key); if (use_spnego) {
key_put(spnego_key); /* BB eventually need to add this */
cifs_dbg(VFS, "spnego not supported for SMB2 yet\n");
rc = -EOPNOTSUPP;
goto out;
} }
sess_data->iov[1].iov_base = ntlmssp_blob;
sess_data->iov[1].iov_len = blob_length;
rc = SMB2_sess_sendreceive(sess_data);
if (rc)
goto out;
rsp = (struct smb2_sess_setup_rsp *)sess_data->iov[0].iov_base;
ses->Suid = rsp->hdr.SessionId;
ses->session_flags = le16_to_cpu(rsp->SessionFlags);
if (ses->session_flags & SMB2_SESSION_FLAG_ENCRYPT_DATA)
cifs_dbg(VFS, "SMB3 encryption not supported yet\n");
rc = SMB2_sess_establish_session(sess_data);
out:
kfree(ntlmssp_blob);
SMB2_sess_free_buffer(sess_data);
kfree(ses->ntlmssp); kfree(ses->ntlmssp);
ses->ntlmssp = NULL;
sess_data->result = rc;
sess_data->func = NULL;
}
static int
SMB2_select_sec(struct cifs_ses *ses, struct SMB2_sess_data *sess_data)
{
if (ses->sectype != Kerberos && ses->sectype != RawNTLMSSP)
ses->sectype = RawNTLMSSP;
switch (ses->sectype) {
case Kerberos:
sess_data->func = SMB2_auth_kerberos;
break;
case RawNTLMSSP:
sess_data->func = SMB2_sess_auth_rawntlmssp_negotiate;
break;
default:
cifs_dbg(VFS, "secType %d not supported!\n", ses->sectype);
return -EOPNOTSUPP;
}
return 0;
}
int
SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses,
const struct nls_table *nls_cp)
{
int rc = 0;
struct TCP_Server_Info *server = ses->server;
struct SMB2_sess_data *sess_data;
cifs_dbg(FYI, "Session Setup\n");
if (!server) {
WARN(1, "%s: server is NULL!\n", __func__);
return -EIO;
}
sess_data = kzalloc(sizeof(struct SMB2_sess_data), GFP_KERNEL);
if (!sess_data)
return -ENOMEM;
rc = SMB2_select_sec(ses, sess_data);
if (rc)
goto out;
sess_data->xid = xid;
sess_data->ses = ses;
sess_data->buf0_type = CIFS_NO_BUFFER;
sess_data->nls_cp = (struct nls_table *) nls_cp;
while (sess_data->func)
sess_data->func(sess_data);
rc = sess_data->result;
out:
kfree(sess_data);
return rc; return rc;
} }
...@@ -1164,7 +1335,7 @@ create_durable_v2_buf(struct cifs_fid *pfid) ...@@ -1164,7 +1335,7 @@ create_durable_v2_buf(struct cifs_fid *pfid)
buf->dcontext.Timeout = 0; /* Should this be configurable by workload */ buf->dcontext.Timeout = 0; /* Should this be configurable by workload */
buf->dcontext.Flags = cpu_to_le32(SMB2_DHANDLE_FLAG_PERSISTENT); buf->dcontext.Flags = cpu_to_le32(SMB2_DHANDLE_FLAG_PERSISTENT);
get_random_bytes(buf->dcontext.CreateGuid, 16); generate_random_uuid(buf->dcontext.CreateGuid);
memcpy(pfid->create_guid, buf->dcontext.CreateGuid, 16); memcpy(pfid->create_guid, buf->dcontext.CreateGuid, 16);
/* SMB2_CREATE_DURABLE_HANDLE_REQUEST is "DH2Q" */ /* SMB2_CREATE_DURABLE_HANDLE_REQUEST is "DH2Q" */
...@@ -2057,6 +2228,7 @@ smb2_async_readv(struct cifs_readdata *rdata) ...@@ -2057,6 +2228,7 @@ smb2_async_readv(struct cifs_readdata *rdata)
if (rdata->credits) { if (rdata->credits) {
buf->CreditCharge = cpu_to_le16(DIV_ROUND_UP(rdata->bytes, buf->CreditCharge = cpu_to_le16(DIV_ROUND_UP(rdata->bytes,
SMB2_MAX_BUFFER_SIZE)); SMB2_MAX_BUFFER_SIZE));
buf->CreditRequest = buf->CreditCharge;
spin_lock(&server->req_lock); spin_lock(&server->req_lock);
server->credits += rdata->credits - server->credits += rdata->credits -
le16_to_cpu(buf->CreditCharge); le16_to_cpu(buf->CreditCharge);
...@@ -2243,6 +2415,7 @@ smb2_async_writev(struct cifs_writedata *wdata, ...@@ -2243,6 +2415,7 @@ smb2_async_writev(struct cifs_writedata *wdata,
if (wdata->credits) { if (wdata->credits) {
req->hdr.CreditCharge = cpu_to_le16(DIV_ROUND_UP(wdata->bytes, req->hdr.CreditCharge = cpu_to_le16(DIV_ROUND_UP(wdata->bytes,
SMB2_MAX_BUFFER_SIZE)); SMB2_MAX_BUFFER_SIZE));
req->hdr.CreditRequest = req->hdr.CreditCharge;
spin_lock(&server->req_lock); spin_lock(&server->req_lock);
server->credits += wdata->credits - server->credits += wdata->credits -
le16_to_cpu(req->hdr.CreditCharge); le16_to_cpu(req->hdr.CreditCharge);
......
...@@ -276,7 +276,7 @@ struct smb2_sess_setup_req { ...@@ -276,7 +276,7 @@ struct smb2_sess_setup_req {
__le32 Channel; __le32 Channel;
__le16 SecurityBufferOffset; __le16 SecurityBufferOffset;
__le16 SecurityBufferLength; __le16 SecurityBufferLength;
__le64 PreviousSessionId; __u64 PreviousSessionId;
__u8 Buffer[1]; /* variable length GSS security buffer */ __u8 Buffer[1]; /* variable length GSS security buffer */
} __packed; } __packed;
......
...@@ -33,7 +33,8 @@ ...@@ -33,7 +33,8 @@
#define MAX_EA_VALUE_SIZE 65535 #define MAX_EA_VALUE_SIZE 65535
#define CIFS_XATTR_CIFS_ACL "system.cifs_acl" #define CIFS_XATTR_CIFS_ACL "system.cifs_acl"
#define CIFS_XATTR_ATTRIB "cifs.dosattrib" /* full name: user.cifs.dosattrib */
#define CIFS_XATTR_CREATETIME "cifs.creationtime" /* user.cifs.creationtime */
/* BB need to add server (Samba e.g) support for security and trusted prefix */ /* BB need to add server (Samba e.g) support for security and trusted prefix */
enum { XATTR_USER, XATTR_CIFS_ACL, XATTR_ACL_ACCESS, XATTR_ACL_DEFAULT }; enum { XATTR_USER, XATTR_CIFS_ACL, XATTR_ACL_ACCESS, XATTR_ACL_DEFAULT };
...@@ -144,6 +145,54 @@ static int cifs_xattr_set(const struct xattr_handler *handler, ...@@ -144,6 +145,54 @@ static int cifs_xattr_set(const struct xattr_handler *handler,
return rc; return rc;
} }
static int cifs_attrib_get(struct dentry *dentry,
struct inode *inode, void *value,
size_t size)
{
ssize_t rc;
__u32 *pattribute;
rc = cifs_revalidate_dentry_attr(dentry);
if (rc)
return rc;
if ((value == NULL) || (size == 0))
return sizeof(__u32);
else if (size < sizeof(__u32))
return -ERANGE;
/* return dos attributes as pseudo xattr */
pattribute = (__u32 *)value;
*pattribute = CIFS_I(inode)->cifsAttrs;
return sizeof(__u32);
}
static int cifs_creation_time_get(struct dentry *dentry, struct inode *inode,
void *value, size_t size)
{
ssize_t rc;
__u64 * pcreatetime;
rc = cifs_revalidate_dentry_attr(dentry);
if (rc)
return rc;
if ((value == NULL) || (size == 0))
return sizeof(__u64);
else if (size < sizeof(__u64))
return -ERANGE;
/* return dos attributes as pseudo xattr */
pcreatetime = (__u64 *)value;
*pcreatetime = CIFS_I(inode)->createtime;
return sizeof(__u64);
return rc;
}
static int cifs_xattr_get(const struct xattr_handler *handler, static int cifs_xattr_get(const struct xattr_handler *handler,
struct dentry *dentry, struct inode *inode, struct dentry *dentry, struct inode *inode,
const char *name, void *value, size_t size) const char *name, void *value, size_t size)
...@@ -168,10 +217,19 @@ static int cifs_xattr_get(const struct xattr_handler *handler, ...@@ -168,10 +217,19 @@ static int cifs_xattr_get(const struct xattr_handler *handler,
rc = -ENOMEM; rc = -ENOMEM;
goto out; goto out;
} }
/* return dos attributes as pseudo xattr */
/* return alt name if available as pseudo attr */ /* return alt name if available as pseudo attr */
switch (handler->flags) { switch (handler->flags) {
case XATTR_USER: case XATTR_USER:
cifs_dbg(FYI, "%s:querying user xattr %s\n", __func__, name);
if (strcmp(name, CIFS_XATTR_ATTRIB) == 0) {
rc = cifs_attrib_get(dentry, inode, value, size);
break;
} else if (strcmp(name, CIFS_XATTR_CREATETIME) == 0) {
rc = cifs_creation_time_get(dentry, inode, value, size);
break;
}
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_XATTR) if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_XATTR)
goto out; goto out;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment