Commit 023f78b0 authored by Linus Torvalds's avatar Linus Torvalds

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

Pull CIFS updates from Steve French:
 "The most visible change in this set is the additional of multi-credit
  support for SMB2/SMB3 which dramatically improves the large file i/o
  performance for these dialects and significantly increases the maximum
  i/o size used on the wire for SMB2/SMB3.

  Also reconnection behavior after network failure is improved"

* 'for-next' of git://git.samba.org/sfrench/cifs-2.6: (35 commits)
  Add worker function to set allocation size
  [CIFS] Fix incorrect hex vs. decimal in some debug print statements
  update CIFS TODO list
  Add Pavel to contributor list in cifs AUTHORS file
  Update cifs version
  CIFS: Fix STATUS_CANNOT_DELETE error mapping for SMB2
  CIFS: Optimize readpages in a short read case on reconnects
  CIFS: Optimize cifs_user_read() in a short read case on reconnects
  CIFS: Improve indentation in cifs_user_read()
  CIFS: Fix possible buffer corruption in cifs_user_read()
  CIFS: Count got bytes in read_into_pages()
  CIFS: Use separate var for the number of bytes got in async read
  CIFS: Indicate reconnect with ECONNABORTED error code
  CIFS: Use multicredits for SMB 2.1/3 reads
  CIFS: Fix rsize usage for sync read
  CIFS: Fix rsize usage in user read
  CIFS: Separate page reading from user read
  CIFS: Fix rsize usage in readpages
  CIFS: Separate page search from readpages
  CIFS: Use multicredits for SMB 2.1/3 writes
  ...
parents 63b12bdb f29ebb47
...@@ -40,6 +40,7 @@ Gunter Kukkukk (testing and suggestions for support of old servers) ...@@ -40,6 +40,7 @@ Gunter Kukkukk (testing and suggestions for support of old servers)
Igor Mammedov (DFS support) Igor Mammedov (DFS support)
Jeff Layton (many, many fixes, as well as great work on the cifs Kerberos code) Jeff Layton (many, many fixes, as well as great work on the cifs Kerberos code)
Scott Lovenberg Scott Lovenberg
Pavel Shilovsky (for great work adding SMB2 support, and various SMB3 features)
Test case and Bug Report contributors Test case and Bug Report contributors
------------------------------------- -------------------------------------
......
Version 1.53 May 20, 2008 Version 2.03 August 1, 2014
A Partial List of Missing Features A Partial List of Missing Features
================================== ==================================
...@@ -7,63 +7,49 @@ Contributions are welcome. There are plenty of opportunities ...@@ -7,63 +7,49 @@ Contributions are welcome. There are plenty of opportunities
for visible, important contributions to this module. Here for visible, important contributions to this module. Here
is a partial list of the known problems and missing features: is a partial list of the known problems and missing features:
a) Support for SecurityDescriptors(Windows/CIFS ACLs) for chmod/chgrp/chown a) SMB3 (and SMB3.02) missing optional features:
so that these operations can be supported to Windows servers - RDMA
- multichannel (started)
- directory leases (improved metadata caching)
- T10 copy offload (copy chunk is only mechanism supported)
- encrypted shares
b) Mapping POSIX ACLs (and eventually NFSv4 ACLs) to CIFS b) improved sparse file support
SecurityDescriptors
c) Better pam/winbind integration (e.g. to handle uid mapping c) Directory entry caching relies on a 1 second timer, rather than
better)
d) Cleanup now unneeded SessSetup code in
fs/cifs/connect.c and add back in NTLMSSP code if any servers
need it
e) fix NTLMv2 signing when two mounts with different users to same
server.
f) Directory entry caching relies on a 1 second timer, rather than
using FindNotify or equivalent. - (started) using FindNotify or equivalent. - (started)
g) quota support (needs minor kernel change since quota calls d) quota support (needs minor kernel change since quota calls
to make it to network filesystems or deviceless filesystems) to make it to network filesystems or deviceless filesystems)
h) investigate sync behavior (including syncpage) and check e) improve support for very old servers (OS/2 and Win9x for example)
for proper behavior of intr/nointr
i) improve support for very old servers (OS/2 and Win9x for example)
Including support for changing the time remotely (utimes command). Including support for changing the time remotely (utimes command).
j) hook lower into the sockets api (as NFS/SunRPC does) to avoid the f) hook lower into the sockets api (as NFS/SunRPC does) to avoid the
extra copy in/out of the socket buffers in some cases. extra copy in/out of the socket buffers in some cases.
k) Better optimize open (and pathbased setfilesize) to reduce the g) Better optimize open (and pathbased setfilesize) to reduce the
oplock breaks coming from windows srv. Piggyback identical file oplock breaks coming from windows srv. Piggyback identical file
opens on top of each other by incrementing reference count rather opens on top of each other by incrementing reference count rather
than resending (helps reduce server resource utilization and avoid than resending (helps reduce server resource utilization and avoid
spurious oplock breaks). spurious oplock breaks).
l) Improve performance of readpages by sending more than one read h) Add support for storing symlink info to Windows servers
at a time when 8 pages or more are requested. In conjuntion
add support for async_cifs_readpages.
m) Add support for storing symlink info to Windows servers
in the Extended Attribute format their SFU clients would recognize. in the Extended Attribute format their SFU clients would recognize.
n) Finish fcntl D_NOTIFY support so kde and gnome file list windows i) Finish inotify support so kde and gnome file list windows
will autorefresh (partially complete by Asser). Needs minor kernel will autorefresh (partially complete by Asser). Needs minor kernel
vfs change to support removing D_NOTIFY on a file. vfs change to support removing D_NOTIFY on a file.
o) Add GUI tool to configure /proc/fs/cifs settings and for display of j) Add GUI tool to configure /proc/fs/cifs settings and for display of
the CIFS statistics (started) the CIFS statistics (started)
p) implement support for security and trusted categories of xattrs k) implement support for security and trusted categories of xattrs
(requires minor protocol extension) to enable better support for SELINUX (requires minor protocol extension) to enable better support for SELINUX
q) Implement O_DIRECT flag on open (already supported on mount) l) Implement O_DIRECT flag on open (already supported on mount)
r) Create UID mapping facility so server UIDs can be mapped on a per m) Create UID mapping facility so server UIDs can be mapped on a per
mount or a per server basis to client UIDs or nobody if no mapping mount or a per server basis to client UIDs or nobody if no mapping
exists. This is helpful when Unix extensions are negotiated to exists. This is helpful when Unix extensions are negotiated to
allow better permission checking when UIDs differ on the server allow better permission checking when UIDs differ on the server
...@@ -71,28 +57,29 @@ and client. Add new protocol request to the CIFS protocol ...@@ -71,28 +57,29 @@ and client. Add new protocol request to the CIFS protocol
standard for asking the server for the corresponding name of a standard for asking the server for the corresponding name of a
particular uid. particular uid.
s) Add support for CIFS Unix and also the newer POSIX extensions to the n) DOS attrs - returned as pseudo-xattr in Samba format (check VFAT and NTFS for this too)
server side for Samba 4.
o) mount check for unmatched uids
t) In support for OS/2 (LANMAN 1.2 and LANMAN2.1 based SMB servers) p) Add support for new vfs entry point for fallocate
need to add ability to set time to server (utimes command)
u) DOS attrs - returned as pseudo-xattr in Samba format (check VFAT and NTFS for this too) q) Add tools to take advantage of cifs/smb3 specific ioctls and features
such as "CopyChunk" (fast server side file copy)
v) mount check for unmatched uids r) encrypted file support
w) Add support for new vfs entry point for fallocate s) improved stats gathering, tools (perhaps integration with nfsometer?)
x) Fix Samba 3 server to handle Linux kernel aio so dbench with lots of t) allow setting more NTFS/SMB3 file attributes remotely (currently limited to compressed
processes can proceed better in parallel (on the server) file attribute via chflags)
y) Fix Samba 3 to handle reads/writes over 127K (and remove the cifs mount u) mount helper GUI (to simplify the various configuration options on mount)
restriction of wsize max being 127K)
KNOWN BUGS (updated April 24, 2007)
KNOWN BUGS
==================================== ====================================
See http://bugzilla.samba.org - search on product "CifsVFS" for See http://bugzilla.samba.org - search on product "CifsVFS" for
current bug list. current bug list. Also check http://bugzilla.kernel.org (Product = File System, Component = CIFS)
1) existing symbolic links (Windows reparse points) are recognized but 1) existing symbolic links (Windows reparse points) are recognized but
can not be created remotely. They are implemented for Samba and those that can not be created remotely. They are implemented for Samba and those that
...@@ -100,30 +87,18 @@ support the CIFS Unix extensions, although earlier versions of Samba ...@@ -100,30 +87,18 @@ support the CIFS Unix extensions, although earlier versions of Samba
overly restrict the pathnames. overly restrict the pathnames.
2) follow_link and readdir code does not follow dfs junctions 2) follow_link and readdir code does not follow dfs junctions
but recognizes them but recognizes them
3) create of new files to FAT partitions on Windows servers can
succeed but still return access denied (appears to be Windows
server not cifs client problem) and has not been reproduced recently.
NTFS partitions do not have this problem.
4) Unix/POSIX capabilities are reset after reconnection, and affect
a few fields in the tree connection but we do do not know which
superblocks to apply these changes to. We should probably walk
the list of superblocks to set these. Also need to check the
flags on the second mount to the same share, and see if we
can do the same trick that NFS does to remount duplicate shares.
Misc testing to do Misc testing to do
================== ==================
1) check out max path names and max path name components against various server 1) check out max path names and max path name components against various server
types. Try nested symlinks (8 deep). Return max path name in stat -f information types. Try nested symlinks (8 deep). Return max path name in stat -f information
2) Modify file portion of ltp so it can run against a mounted network 2) Improve xfstest's cifs enablement and adapt xfstests where needed to test
share and run it against cifs vfs in automated fashion. cifs better
3) Additional performance testing and optimization using iozone and similar - 3) Additional performance testing and optimization using iozone and similar -
there are some easy changes that can be done to parallelize sequential writes, there are some easy changes that can be done to parallelize sequential writes,
and when signing is disabled to request larger read sizes (larger than and when signing is disabled to request larger read sizes (larger than
negotiated size) and send larger write sizes to modern servers. negotiated size) and send larger write sizes to modern servers.
4) More exhaustively test against less common servers. More testing 4) More exhaustively test against less common servers
against Windows 9x, Windows ME servers.
...@@ -213,7 +213,7 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v) ...@@ -213,7 +213,7 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
tcon->nativeFileSystem); tcon->nativeFileSystem);
} }
seq_printf(m, "DevInfo: 0x%x Attributes: 0x%x" seq_printf(m, "DevInfo: 0x%x Attributes: 0x%x"
"\n\tPathComponentMax: %d Status: 0x%d", "\n\tPathComponentMax: %d Status: %d",
le32_to_cpu(tcon->fsDevInfo.DeviceCharacteristics), le32_to_cpu(tcon->fsDevInfo.DeviceCharacteristics),
le32_to_cpu(tcon->fsAttrInfo.Attributes), le32_to_cpu(tcon->fsAttrInfo.Attributes),
le32_to_cpu(tcon->fsAttrInfo.MaxPathNameComponentLength), le32_to_cpu(tcon->fsAttrInfo.MaxPathNameComponentLength),
......
...@@ -136,5 +136,5 @@ extern long cifs_ioctl(struct file *filep, unsigned int cmd, unsigned long arg); ...@@ -136,5 +136,5 @@ extern long cifs_ioctl(struct file *filep, unsigned int cmd, unsigned long arg);
extern const struct export_operations cifs_export_ops; extern const struct export_operations cifs_export_ops;
#endif /* CONFIG_CIFS_NFSD_EXPORT */ #endif /* CONFIG_CIFS_NFSD_EXPORT */
#define CIFS_VERSION "2.03" #define CIFS_VERSION "2.04"
#endif /* _CIFSFS_H */ #endif /* _CIFSFS_H */
...@@ -404,6 +404,11 @@ struct smb_version_operations { ...@@ -404,6 +404,11 @@ struct smb_version_operations {
const struct cifs_fid *, u32 *); const struct cifs_fid *, u32 *);
int (*set_acl)(struct cifs_ntsd *, __u32, struct inode *, const char *, int (*set_acl)(struct cifs_ntsd *, __u32, struct inode *, const char *,
int); int);
/* writepages retry size */
unsigned int (*wp_retry_size)(struct inode *);
/* get mtu credits */
int (*wait_mtu_credits)(struct TCP_Server_Info *, unsigned int,
unsigned int *, unsigned int *);
}; };
struct smb_version_values { struct smb_version_values {
...@@ -639,6 +644,16 @@ add_credits(struct TCP_Server_Info *server, const unsigned int add, ...@@ -639,6 +644,16 @@ add_credits(struct TCP_Server_Info *server, const unsigned int add,
server->ops->add_credits(server, add, optype); server->ops->add_credits(server, add, optype);
} }
static inline void
add_credits_and_wake_if(struct TCP_Server_Info *server, const unsigned int add,
const int optype)
{
if (add) {
server->ops->add_credits(server, add, optype);
wake_up(&server->request_q);
}
}
static inline void static inline void
set_credits(struct TCP_Server_Info *server, const int val) set_credits(struct TCP_Server_Info *server, const int val)
{ {
...@@ -1044,6 +1059,7 @@ struct cifs_readdata { ...@@ -1044,6 +1059,7 @@ struct cifs_readdata {
struct address_space *mapping; struct address_space *mapping;
__u64 offset; __u64 offset;
unsigned int bytes; unsigned int bytes;
unsigned int got_bytes;
pid_t pid; pid_t pid;
int result; int result;
struct work_struct work; struct work_struct work;
...@@ -1053,6 +1069,7 @@ struct cifs_readdata { ...@@ -1053,6 +1069,7 @@ struct cifs_readdata {
struct kvec iov; struct kvec iov;
unsigned int pagesz; unsigned int pagesz;
unsigned int tailsz; unsigned int tailsz;
unsigned int credits;
unsigned int nr_pages; unsigned int nr_pages;
struct page *pages[]; struct page *pages[];
}; };
...@@ -1073,6 +1090,7 @@ struct cifs_writedata { ...@@ -1073,6 +1090,7 @@ struct cifs_writedata {
int result; int result;
unsigned int pagesz; unsigned int pagesz;
unsigned int tailsz; unsigned int tailsz;
unsigned int credits;
unsigned int nr_pages; unsigned int nr_pages;
struct page *pages[]; struct page *pages[];
}; };
...@@ -1398,6 +1416,7 @@ static inline void free_dfs_info_array(struct dfs_info3_param *param, ...@@ -1398,6 +1416,7 @@ static inline void free_dfs_info_array(struct dfs_info3_param *param,
#define CIFS_OBREAK_OP 0x0100 /* oplock break request */ #define CIFS_OBREAK_OP 0x0100 /* oplock break request */
#define CIFS_NEG_OP 0x0200 /* negotiate request */ #define CIFS_NEG_OP 0x0200 /* negotiate request */
#define CIFS_OP_MASK 0x0380 /* mask request type */ #define CIFS_OP_MASK 0x0380 /* mask request type */
#define CIFS_HAS_CREDITS 0x0400 /* already has credits */
/* Security Flags: indicate type of session setup needed */ /* Security Flags: indicate type of session setup needed */
#define CIFSSEC_MAY_SIGN 0x00001 #define CIFSSEC_MAY_SIGN 0x00001
......
...@@ -36,6 +36,7 @@ extern struct smb_hdr *cifs_buf_get(void); ...@@ -36,6 +36,7 @@ extern struct smb_hdr *cifs_buf_get(void);
extern void cifs_buf_release(void *); extern void cifs_buf_release(void *);
extern struct smb_hdr *cifs_small_buf_get(void); extern struct smb_hdr *cifs_small_buf_get(void);
extern void cifs_small_buf_release(void *); extern void cifs_small_buf_release(void *);
extern void free_rsp_buf(int, void *);
extern void cifs_rqst_page_to_kvec(struct smb_rqst *rqst, unsigned int idx, extern void cifs_rqst_page_to_kvec(struct smb_rqst *rqst, unsigned int idx,
struct kvec *iov); struct kvec *iov);
extern int smb_send(struct TCP_Server_Info *, struct smb_hdr *, extern int smb_send(struct TCP_Server_Info *, struct smb_hdr *,
...@@ -89,6 +90,9 @@ extern struct mid_q_entry *cifs_setup_async_request(struct TCP_Server_Info *, ...@@ -89,6 +90,9 @@ extern struct mid_q_entry *cifs_setup_async_request(struct TCP_Server_Info *,
struct smb_rqst *); struct smb_rqst *);
extern int cifs_check_receive(struct mid_q_entry *mid, extern int cifs_check_receive(struct mid_q_entry *mid,
struct TCP_Server_Info *server, bool log_error); struct TCP_Server_Info *server, bool log_error);
extern int cifs_wait_mtu_credits(struct TCP_Server_Info *server,
unsigned int size, unsigned int *num,
unsigned int *credits);
extern int SendReceive2(const unsigned int /* xid */ , struct cifs_ses *, extern int SendReceive2(const unsigned int /* xid */ , struct cifs_ses *,
struct kvec *, int /* nvec to send */, struct kvec *, int /* nvec to send */,
int * /* type of buf returned */ , const int flags); int * /* type of buf returned */ , const int flags);
......
...@@ -196,10 +196,6 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command) ...@@ -196,10 +196,6 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command)
if (rc) if (rc)
goto out; goto out;
/*
* FIXME: check if wsize needs updated due to negotiated smb buffer
* size shrinking
*/
atomic_inc(&tconInfoReconnectCount); atomic_inc(&tconInfoReconnectCount);
/* tell server Unix caps we support */ /* tell server Unix caps we support */
...@@ -1517,7 +1513,6 @@ cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid) ...@@ -1517,7 +1513,6 @@ cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid)
return length; return length;
server->total_read += length; server->total_read += length;
rdata->bytes = length;
cifs_dbg(FYI, "total_read=%u buflen=%u remaining=%u\n", cifs_dbg(FYI, "total_read=%u buflen=%u remaining=%u\n",
server->total_read, buflen, data_len); server->total_read, buflen, data_len);
...@@ -1560,12 +1555,18 @@ cifs_readv_callback(struct mid_q_entry *mid) ...@@ -1560,12 +1555,18 @@ cifs_readv_callback(struct mid_q_entry *mid)
rc); rc);
} }
/* FIXME: should this be counted toward the initiating task? */ /* FIXME: should this be counted toward the initiating task? */
task_io_account_read(rdata->bytes); task_io_account_read(rdata->got_bytes);
cifs_stats_bytes_read(tcon, rdata->bytes); cifs_stats_bytes_read(tcon, rdata->got_bytes);
break; break;
case MID_REQUEST_SUBMITTED: case MID_REQUEST_SUBMITTED:
case MID_RETRY_NEEDED: case MID_RETRY_NEEDED:
rdata->result = -EAGAIN; rdata->result = -EAGAIN;
if (server->sign && rdata->got_bytes)
/* reset bytes number since we can not check a sign */
rdata->got_bytes = 0;
/* FIXME: should this be counted toward the initiating task? */
task_io_account_read(rdata->got_bytes);
cifs_stats_bytes_read(tcon, rdata->got_bytes);
break; break;
default: default:
rdata->result = -EIO; rdata->result = -EIO;
...@@ -1734,10 +1735,7 @@ CIFSSMBRead(const unsigned int xid, struct cifs_io_parms *io_parms, ...@@ -1734,10 +1735,7 @@ CIFSSMBRead(const unsigned int xid, struct cifs_io_parms *io_parms,
/* cifs_small_buf_release(pSMB); */ /* Freed earlier now in SendReceive2 */ /* cifs_small_buf_release(pSMB); */ /* Freed earlier now in SendReceive2 */
if (*buf) { if (*buf) {
if (resp_buf_type == CIFS_SMALL_BUFFER) free_rsp_buf(resp_buf_type, iov[0].iov_base);
cifs_small_buf_release(iov[0].iov_base);
else if (resp_buf_type == CIFS_LARGE_BUFFER)
cifs_buf_release(iov[0].iov_base);
} else if (resp_buf_type != CIFS_NO_BUFFER) { } else if (resp_buf_type != CIFS_NO_BUFFER) {
/* return buffer to caller to free */ /* return buffer to caller to free */
*buf = iov[0].iov_base; *buf = iov[0].iov_base;
...@@ -1899,28 +1897,80 @@ cifs_writedata_release(struct kref *refcount) ...@@ -1899,28 +1897,80 @@ cifs_writedata_release(struct kref *refcount)
static void static void
cifs_writev_requeue(struct cifs_writedata *wdata) cifs_writev_requeue(struct cifs_writedata *wdata)
{ {
int i, rc; int i, rc = 0;
struct inode *inode = wdata->cfile->dentry->d_inode; struct inode *inode = wdata->cfile->dentry->d_inode;
struct TCP_Server_Info *server; struct TCP_Server_Info *server;
unsigned int rest_len;
for (i = 0; i < wdata->nr_pages; i++) { server = tlink_tcon(wdata->cfile->tlink)->ses->server;
lock_page(wdata->pages[i]); i = 0;
clear_page_dirty_for_io(wdata->pages[i]); rest_len = wdata->bytes;
do {
struct cifs_writedata *wdata2;
unsigned int j, nr_pages, wsize, tailsz, cur_len;
wsize = server->ops->wp_retry_size(inode);
if (wsize < rest_len) {
nr_pages = wsize / PAGE_CACHE_SIZE;
if (!nr_pages) {
rc = -ENOTSUPP;
break;
}
cur_len = nr_pages * PAGE_CACHE_SIZE;
tailsz = PAGE_CACHE_SIZE;
} else {
nr_pages = DIV_ROUND_UP(rest_len, PAGE_CACHE_SIZE);
cur_len = rest_len;
tailsz = rest_len - (nr_pages - 1) * PAGE_CACHE_SIZE;
} }
do { wdata2 = cifs_writedata_alloc(nr_pages, cifs_writev_complete);
server = tlink_tcon(wdata->cfile->tlink)->ses->server; if (!wdata2) {
rc = server->ops->async_writev(wdata, cifs_writedata_release); rc = -ENOMEM;
} while (rc == -EAGAIN); break;
}
for (i = 0; i < wdata->nr_pages; i++) { for (j = 0; j < nr_pages; j++) {
unlock_page(wdata->pages[i]); wdata2->pages[j] = wdata->pages[i + j];
if (rc != 0) { lock_page(wdata2->pages[j]);
SetPageError(wdata->pages[i]); clear_page_dirty_for_io(wdata2->pages[j]);
end_page_writeback(wdata->pages[i]);
page_cache_release(wdata->pages[i]);
} }
wdata2->sync_mode = wdata->sync_mode;
wdata2->nr_pages = nr_pages;
wdata2->offset = page_offset(wdata2->pages[0]);
wdata2->pagesz = PAGE_CACHE_SIZE;
wdata2->tailsz = tailsz;
wdata2->bytes = cur_len;
wdata2->cfile = find_writable_file(CIFS_I(inode), false);
if (!wdata2->cfile) {
cifs_dbg(VFS, "No writable handles for inode\n");
rc = -EBADF;
break;
} }
wdata2->pid = wdata2->cfile->pid;
rc = server->ops->async_writev(wdata2, cifs_writedata_release);
for (j = 0; j < nr_pages; j++) {
unlock_page(wdata2->pages[j]);
if (rc != 0 && rc != -EAGAIN) {
SetPageError(wdata2->pages[j]);
end_page_writeback(wdata2->pages[j]);
page_cache_release(wdata2->pages[j]);
}
}
if (rc) {
kref_put(&wdata2->refcount, cifs_writedata_release);
if (rc == -EAGAIN)
continue;
break;
}
rest_len -= cur_len;
i += nr_pages;
} while (i < wdata->nr_pages);
mapping_set_error(inode->i_mapping, rc); mapping_set_error(inode->i_mapping, rc);
kref_put(&wdata->refcount, cifs_writedata_release); kref_put(&wdata->refcount, cifs_writedata_release);
...@@ -2203,10 +2253,7 @@ CIFSSMBWrite2(const unsigned int xid, struct cifs_io_parms *io_parms, ...@@ -2203,10 +2253,7 @@ CIFSSMBWrite2(const unsigned int xid, struct cifs_io_parms *io_parms,
} }
/* cifs_small_buf_release(pSMB); */ /* Freed earlier now in SendReceive2 */ /* cifs_small_buf_release(pSMB); */ /* Freed earlier now in SendReceive2 */
if (resp_buf_type == CIFS_SMALL_BUFFER) free_rsp_buf(resp_buf_type, iov[0].iov_base);
cifs_small_buf_release(iov[0].iov_base);
else if (resp_buf_type == CIFS_LARGE_BUFFER)
cifs_buf_release(iov[0].iov_base);
/* Note: On -EAGAIN error only caller can retry on handle based calls /* Note: On -EAGAIN error only caller can retry on handle based calls
since file handle passed in no longer valid */ since file handle passed in no longer valid */
...@@ -2451,10 +2498,7 @@ CIFSSMBPosixLock(const unsigned int xid, struct cifs_tcon *tcon, ...@@ -2451,10 +2498,7 @@ CIFSSMBPosixLock(const unsigned int xid, struct cifs_tcon *tcon,
if (pSMB) if (pSMB)
cifs_small_buf_release(pSMB); cifs_small_buf_release(pSMB);
if (resp_buf_type == CIFS_SMALL_BUFFER) free_rsp_buf(resp_buf_type, iov[0].iov_base);
cifs_small_buf_release(iov[0].iov_base);
else if (resp_buf_type == CIFS_LARGE_BUFFER)
cifs_buf_release(iov[0].iov_base);
/* Note: On -EAGAIN error only caller can retry on handle based calls /* Note: On -EAGAIN error only caller can retry on handle based calls
since file handle passed in no longer valid */ since file handle passed in no longer valid */
...@@ -3838,10 +3882,7 @@ CIFSSMBGetCIFSACL(const unsigned int xid, struct cifs_tcon *tcon, __u16 fid, ...@@ -3838,10 +3882,7 @@ CIFSSMBGetCIFSACL(const unsigned int xid, struct cifs_tcon *tcon, __u16 fid,
} }
} }
qsec_out: qsec_out:
if (buf_type == CIFS_SMALL_BUFFER) free_rsp_buf(buf_type, iov[0].iov_base);
cifs_small_buf_release(iov[0].iov_base);
else if (buf_type == CIFS_LARGE_BUFFER)
cifs_buf_release(iov[0].iov_base);
/* cifs_small_buf_release(pSMB); */ /* Freed earlier now in SendReceive2 */ /* cifs_small_buf_release(pSMB); */ /* Freed earlier now in SendReceive2 */
return rc; return rc;
} }
......
...@@ -557,7 +557,7 @@ cifs_readv_from_socket(struct TCP_Server_Info *server, struct kvec *iov_orig, ...@@ -557,7 +557,7 @@ cifs_readv_from_socket(struct TCP_Server_Info *server, struct kvec *iov_orig,
try_to_freeze(); try_to_freeze();
if (server_unresponsive(server)) { if (server_unresponsive(server)) {
total_read = -EAGAIN; total_read = -ECONNABORTED;
break; break;
} }
...@@ -571,7 +571,7 @@ cifs_readv_from_socket(struct TCP_Server_Info *server, struct kvec *iov_orig, ...@@ -571,7 +571,7 @@ cifs_readv_from_socket(struct TCP_Server_Info *server, struct kvec *iov_orig,
break; break;
} else if (server->tcpStatus == CifsNeedReconnect) { } else if (server->tcpStatus == CifsNeedReconnect) {
cifs_reconnect(server); cifs_reconnect(server);
total_read = -EAGAIN; total_read = -ECONNABORTED;
break; break;
} else if (length == -ERESTARTSYS || } else if (length == -ERESTARTSYS ||
length == -EAGAIN || length == -EAGAIN ||
...@@ -588,7 +588,7 @@ cifs_readv_from_socket(struct TCP_Server_Info *server, struct kvec *iov_orig, ...@@ -588,7 +588,7 @@ cifs_readv_from_socket(struct TCP_Server_Info *server, struct kvec *iov_orig,
cifs_dbg(FYI, "Received no data or error: expecting %d\n" cifs_dbg(FYI, "Received no data or error: expecting %d\n"
"got %d", to_read, length); "got %d", to_read, length);
cifs_reconnect(server); cifs_reconnect(server);
total_read = -EAGAIN; total_read = -ECONNABORTED;
break; break;
} }
} }
...@@ -786,7 +786,7 @@ standard_receive3(struct TCP_Server_Info *server, struct mid_q_entry *mid) ...@@ -786,7 +786,7 @@ standard_receive3(struct TCP_Server_Info *server, struct mid_q_entry *mid)
cifs_dbg(VFS, "SMB response too long (%u bytes)\n", pdu_length); cifs_dbg(VFS, "SMB response too long (%u bytes)\n", pdu_length);
cifs_reconnect(server); cifs_reconnect(server);
wake_up(&server->response_q); wake_up(&server->response_q);
return -EAGAIN; return -ECONNABORTED;
} }
/* switch to large buffer if too big for a small one */ /* switch to large buffer if too big for a small one */
......
...@@ -1670,8 +1670,8 @@ cifs_write(struct cifsFileInfo *open_file, __u32 pid, const char *write_data, ...@@ -1670,8 +1670,8 @@ cifs_write(struct cifsFileInfo *open_file, __u32 pid, const char *write_data,
break; break;
} }
len = min((size_t)cifs_sb->wsize, len = min(server->ops->wp_retry_size(dentry->d_inode),
write_size - total_written); (unsigned int)write_size - total_written);
/* iov[0] is reserved for smb header */ /* iov[0] is reserved for smb header */
iov[1].iov_base = (char *)write_data + total_written; iov[1].iov_base = (char *)write_data + total_written;
iov[1].iov_len = len; iov[1].iov_len = len;
...@@ -1878,49 +1878,19 @@ static int cifs_partialpagewrite(struct page *page, unsigned from, unsigned to) ...@@ -1878,49 +1878,19 @@ static int cifs_partialpagewrite(struct page *page, unsigned from, unsigned to)
return rc; return rc;
} }
static int cifs_writepages(struct address_space *mapping, static struct cifs_writedata *
struct writeback_control *wbc) wdata_alloc_and_fillpages(pgoff_t tofind, struct address_space *mapping,
pgoff_t end, pgoff_t *index,
unsigned int *found_pages)
{ {
struct cifs_sb_info *cifs_sb = CIFS_SB(mapping->host->i_sb); unsigned int nr_pages;
bool done = false, scanned = false, range_whole = false;
pgoff_t end, index;
struct cifs_writedata *wdata;
struct TCP_Server_Info *server;
struct page *page;
int rc = 0;
/*
* If wsize is smaller than the page cache size, default to writing
* one page at a time via cifs_writepage
*/
if (cifs_sb->wsize < PAGE_CACHE_SIZE)
return generic_writepages(mapping, wbc);
if (wbc->range_cyclic) {
index = mapping->writeback_index; /* Start from prev offset */
end = -1;
} else {
index = wbc->range_start >> PAGE_CACHE_SHIFT;
end = wbc->range_end >> PAGE_CACHE_SHIFT;
if (wbc->range_start == 0 && wbc->range_end == LLONG_MAX)
range_whole = true;
scanned = true;
}
retry:
while (!done && index <= end) {
unsigned int i, nr_pages, found_pages;
pgoff_t next = 0, tofind;
struct page **pages; struct page **pages;
struct cifs_writedata *wdata;
tofind = min((cifs_sb->wsize / PAGE_CACHE_SIZE) - 1,
end - index) + 1;
wdata = cifs_writedata_alloc((unsigned int)tofind, wdata = cifs_writedata_alloc((unsigned int)tofind,
cifs_writev_complete); cifs_writev_complete);
if (!wdata) { if (!wdata)
rc = -ENOMEM; return NULL;
break;
}
/* /*
* find_get_pages_tag seems to return a max of 256 on each * find_get_pages_tag seems to return a max of 256 on each
...@@ -1928,23 +1898,29 @@ static int cifs_writepages(struct address_space *mapping, ...@@ -1928,23 +1898,29 @@ static int cifs_writepages(struct address_space *mapping,
* fill the array or the wsize is effectively limited to * fill the array or the wsize is effectively limited to
* 256 * PAGE_CACHE_SIZE. * 256 * PAGE_CACHE_SIZE.
*/ */
found_pages = 0; *found_pages = 0;
pages = wdata->pages; pages = wdata->pages;
do { do {
nr_pages = find_get_pages_tag(mapping, &index, nr_pages = find_get_pages_tag(mapping, index,
PAGECACHE_TAG_DIRTY, PAGECACHE_TAG_DIRTY, tofind,
tofind, pages); pages);
found_pages += nr_pages; *found_pages += nr_pages;
tofind -= nr_pages; tofind -= nr_pages;
pages += nr_pages; pages += nr_pages;
} while (nr_pages && tofind && index <= end); } while (nr_pages && tofind && *index <= end);
if (found_pages == 0) { return wdata;
kref_put(&wdata->refcount, cifs_writedata_release); }
break;
} static unsigned int
wdata_prepare_pages(struct cifs_writedata *wdata, unsigned int found_pages,
struct address_space *mapping,
struct writeback_control *wbc,
pgoff_t end, pgoff_t *index, pgoff_t *next, bool *done)
{
unsigned int nr_pages = 0, i;
struct page *page;
nr_pages = 0;
for (i = 0; i < found_pages; i++) { for (i = 0; i < found_pages; i++) {
page = wdata->pages[i]; page = wdata->pages[i];
/* /*
...@@ -1966,12 +1942,12 @@ static int cifs_writepages(struct address_space *mapping, ...@@ -1966,12 +1942,12 @@ static int cifs_writepages(struct address_space *mapping,
} }
if (!wbc->range_cyclic && page->index > end) { if (!wbc->range_cyclic && page->index > end) {
done = true; *done = true;
unlock_page(page); unlock_page(page);
break; break;
} }
if (next && (page->index != next)) { if (*next && (page->index != *next)) {
/* Not next consecutive page */ /* Not next consecutive page */
unlock_page(page); unlock_page(page);
break; break;
...@@ -1991,22 +1967,21 @@ static int cifs_writepages(struct address_space *mapping, ...@@ -1991,22 +1967,21 @@ static int cifs_writepages(struct address_space *mapping,
* See cifs_writepage() for more commentary. * See cifs_writepage() for more commentary.
*/ */
set_page_writeback(page); set_page_writeback(page);
if (page_offset(page) >= i_size_read(mapping->host)) { if (page_offset(page) >= i_size_read(mapping->host)) {
done = true; *done = true;
unlock_page(page); unlock_page(page);
end_page_writeback(page); end_page_writeback(page);
break; break;
} }
wdata->pages[i] = page; wdata->pages[i] = page;
next = page->index + 1; *next = page->index + 1;
++nr_pages; ++nr_pages;
} }
/* reset index to refind any pages skipped */ /* reset index to refind any pages skipped */
if (nr_pages == 0) if (nr_pages == 0)
index = wdata->pages[0]->index + 1; *index = wdata->pages[0]->index + 1;
/* put any pages we aren't going to use */ /* put any pages we aren't going to use */
for (i = nr_pages; i < found_pages; i++) { for (i = nr_pages; i < found_pages; i++) {
...@@ -2014,44 +1989,115 @@ static int cifs_writepages(struct address_space *mapping, ...@@ -2014,44 +1989,115 @@ static int cifs_writepages(struct address_space *mapping,
wdata->pages[i] = NULL; wdata->pages[i] = NULL;
} }
/* nothing to write? */ return nr_pages;
if (nr_pages == 0) { }
kref_put(&wdata->refcount, cifs_writedata_release);
continue; static int
} wdata_send_pages(struct cifs_writedata *wdata, unsigned int nr_pages,
struct address_space *mapping, struct writeback_control *wbc)
{
int rc = 0;
struct TCP_Server_Info *server;
unsigned int i;
wdata->sync_mode = wbc->sync_mode; wdata->sync_mode = wbc->sync_mode;
wdata->nr_pages = nr_pages; wdata->nr_pages = nr_pages;
wdata->offset = page_offset(wdata->pages[0]); wdata->offset = page_offset(wdata->pages[0]);
wdata->pagesz = PAGE_CACHE_SIZE; wdata->pagesz = PAGE_CACHE_SIZE;
wdata->tailsz = wdata->tailsz = min(i_size_read(mapping->host) -
min(i_size_read(mapping->host) -
page_offset(wdata->pages[nr_pages - 1]), page_offset(wdata->pages[nr_pages - 1]),
(loff_t)PAGE_CACHE_SIZE); (loff_t)PAGE_CACHE_SIZE);
wdata->bytes = ((nr_pages - 1) * PAGE_CACHE_SIZE) + wdata->bytes = ((nr_pages - 1) * PAGE_CACHE_SIZE) + wdata->tailsz;
wdata->tailsz;
do {
if (wdata->cfile != NULL) if (wdata->cfile != NULL)
cifsFileInfo_put(wdata->cfile); cifsFileInfo_put(wdata->cfile);
wdata->cfile = find_writable_file(CIFS_I(mapping->host), wdata->cfile = find_writable_file(CIFS_I(mapping->host), false);
false);
if (!wdata->cfile) { if (!wdata->cfile) {
cifs_dbg(VFS, "No writable handles for inode\n"); cifs_dbg(VFS, "No writable handles for inode\n");
rc = -EBADF; rc = -EBADF;
break; } else {
}
wdata->pid = wdata->cfile->pid; wdata->pid = wdata->cfile->pid;
server = tlink_tcon(wdata->cfile->tlink)->ses->server; server = tlink_tcon(wdata->cfile->tlink)->ses->server;
rc = server->ops->async_writev(wdata, rc = server->ops->async_writev(wdata, cifs_writedata_release);
cifs_writedata_release); }
} while (wbc->sync_mode == WB_SYNC_ALL && rc == -EAGAIN);
for (i = 0; i < nr_pages; ++i) for (i = 0; i < nr_pages; ++i)
unlock_page(wdata->pages[i]); unlock_page(wdata->pages[i]);
return rc;
}
static int cifs_writepages(struct address_space *mapping,
struct writeback_control *wbc)
{
struct cifs_sb_info *cifs_sb = CIFS_SB(mapping->host->i_sb);
struct TCP_Server_Info *server;
bool done = false, scanned = false, range_whole = false;
pgoff_t end, index;
struct cifs_writedata *wdata;
int rc = 0;
/*
* If wsize is smaller than the page cache size, default to writing
* one page at a time via cifs_writepage
*/
if (cifs_sb->wsize < PAGE_CACHE_SIZE)
return generic_writepages(mapping, wbc);
if (wbc->range_cyclic) {
index = mapping->writeback_index; /* Start from prev offset */
end = -1;
} else {
index = wbc->range_start >> PAGE_CACHE_SHIFT;
end = wbc->range_end >> PAGE_CACHE_SHIFT;
if (wbc->range_start == 0 && wbc->range_end == LLONG_MAX)
range_whole = true;
scanned = true;
}
server = cifs_sb_master_tcon(cifs_sb)->ses->server;
retry:
while (!done && index <= end) {
unsigned int i, nr_pages, found_pages, wsize, credits;
pgoff_t next = 0, tofind, saved_index = index;
rc = server->ops->wait_mtu_credits(server, cifs_sb->wsize,
&wsize, &credits);
if (rc)
break;
tofind = min((wsize / PAGE_CACHE_SIZE) - 1, end - index) + 1;
wdata = wdata_alloc_and_fillpages(tofind, mapping, end, &index,
&found_pages);
if (!wdata) {
rc = -ENOMEM;
add_credits_and_wake_if(server, credits, 0);
break;
}
if (found_pages == 0) {
kref_put(&wdata->refcount, cifs_writedata_release);
add_credits_and_wake_if(server, credits, 0);
break;
}
nr_pages = wdata_prepare_pages(wdata, found_pages, mapping, wbc,
end, &index, &next, &done);
/* nothing to write? */
if (nr_pages == 0) {
kref_put(&wdata->refcount, cifs_writedata_release);
add_credits_and_wake_if(server, credits, 0);
continue;
}
wdata->credits = credits;
rc = wdata_send_pages(wdata, nr_pages, mapping, wbc);
/* send failure -- clean up the mess */ /* send failure -- clean up the mess */
if (rc != 0) { if (rc != 0) {
add_credits_and_wake_if(server, wdata->credits, 0);
for (i = 0; i < nr_pages; ++i) { for (i = 0; i < nr_pages; ++i) {
if (rc == -EAGAIN) if (rc == -EAGAIN)
redirty_page_for_writepage(wbc, redirty_page_for_writepage(wbc,
...@@ -2066,6 +2112,11 @@ static int cifs_writepages(struct address_space *mapping, ...@@ -2066,6 +2112,11 @@ static int cifs_writepages(struct address_space *mapping,
} }
kref_put(&wdata->refcount, cifs_writedata_release); kref_put(&wdata->refcount, cifs_writedata_release);
if (wbc->sync_mode == WB_SYNC_ALL && rc == -EAGAIN) {
index = saved_index;
continue;
}
wbc->nr_to_write -= nr_pages; wbc->nr_to_write -= nr_pages;
if (wbc->nr_to_write <= 0) if (wbc->nr_to_write <= 0)
done = true; done = true;
...@@ -2362,123 +2413,109 @@ cifs_uncached_writev_complete(struct work_struct *work) ...@@ -2362,123 +2413,109 @@ cifs_uncached_writev_complete(struct work_struct *work)
kref_put(&wdata->refcount, cifs_uncached_writedata_release); kref_put(&wdata->refcount, cifs_uncached_writedata_release);
} }
/* attempt to send write to server, retry on any -EAGAIN errors */
static int static int
cifs_uncached_retry_writev(struct cifs_writedata *wdata) wdata_fill_from_iovec(struct cifs_writedata *wdata, struct iov_iter *from,
size_t *len, unsigned long *num_pages)
{ {
int rc; size_t save_len, copied, bytes, cur_len = *len;
struct TCP_Server_Info *server; unsigned long i, nr_pages = *num_pages;
server = tlink_tcon(wdata->cfile->tlink)->ses->server;
do { save_len = cur_len;
if (wdata->cfile->invalidHandle) { for (i = 0; i < nr_pages; i++) {
rc = cifs_reopen_file(wdata->cfile, false); bytes = min_t(const size_t, cur_len, PAGE_SIZE);
if (rc != 0) copied = copy_page_from_iter(wdata->pages[i], 0, bytes, from);
continue; cur_len -= copied;
/*
* If we didn't copy as much as we expected, then that
* may mean we trod into an unmapped area. Stop copying
* at that point. On the next pass through the big
* loop, we'll likely end up getting a zero-length
* write and bailing out of it.
*/
if (copied < bytes)
break;
} }
rc = server->ops->async_writev(wdata, cur_len = save_len - cur_len;
cifs_uncached_writedata_release); *len = cur_len;
} while (rc == -EAGAIN);
return rc; /*
* If we have no data to send, then that probably means that
* the copy above failed altogether. That's most likely because
* the address in the iovec was bogus. Return -EFAULT and let
* the caller free anything we allocated and bail out.
*/
if (!cur_len)
return -EFAULT;
/*
* i + 1 now represents the number of pages we actually used in
* the copy phase above.
*/
*num_pages = i + 1;
return 0;
} }
static ssize_t static int
cifs_iovec_write(struct file *file, struct iov_iter *from, loff_t *poffset) cifs_write_from_iter(loff_t offset, size_t len, struct iov_iter *from,
struct cifsFileInfo *open_file,
struct cifs_sb_info *cifs_sb, struct list_head *wdata_list)
{ {
unsigned long nr_pages, i; int rc = 0;
size_t bytes, copied, len, cur_len; size_t cur_len;
ssize_t total_written = 0; unsigned long nr_pages, num_pages, i;
loff_t offset; struct cifs_writedata *wdata;
struct cifsFileInfo *open_file; struct iov_iter saved_from;
struct cifs_tcon *tcon; loff_t saved_offset = offset;
struct cifs_sb_info *cifs_sb;
struct cifs_writedata *wdata, *tmp;
struct list_head wdata_list;
int rc;
pid_t pid; pid_t pid;
struct TCP_Server_Info *server;
len = iov_iter_count(from);
rc = generic_write_checks(file, poffset, &len, 0);
if (rc)
return rc;
if (!len)
return 0;
iov_iter_truncate(from, len);
INIT_LIST_HEAD(&wdata_list);
cifs_sb = CIFS_SB(file->f_path.dentry->d_sb);
open_file = file->private_data;
tcon = tlink_tcon(open_file->tlink);
if (!tcon->ses->server->ops->async_writev)
return -ENOSYS;
offset = *poffset;
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_RWPIDFORWARD) if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_RWPIDFORWARD)
pid = open_file->pid; pid = open_file->pid;
else else
pid = current->tgid; pid = current->tgid;
server = tlink_tcon(open_file->tlink)->ses->server;
memcpy(&saved_from, from, sizeof(struct iov_iter));
do { do {
size_t save_len; unsigned int wsize, credits;
rc = server->ops->wait_mtu_credits(server, cifs_sb->wsize,
&wsize, &credits);
if (rc)
break;
nr_pages = get_numpages(cifs_sb->wsize, len, &cur_len); nr_pages = get_numpages(wsize, len, &cur_len);
wdata = cifs_writedata_alloc(nr_pages, wdata = cifs_writedata_alloc(nr_pages,
cifs_uncached_writev_complete); cifs_uncached_writev_complete);
if (!wdata) { if (!wdata) {
rc = -ENOMEM; rc = -ENOMEM;
add_credits_and_wake_if(server, credits, 0);
break; break;
} }
rc = cifs_write_allocate_pages(wdata->pages, nr_pages); rc = cifs_write_allocate_pages(wdata->pages, nr_pages);
if (rc) { if (rc) {
kfree(wdata); kfree(wdata);
add_credits_and_wake_if(server, credits, 0);
break; break;
} }
save_len = cur_len; num_pages = nr_pages;
for (i = 0; i < nr_pages; i++) { rc = wdata_fill_from_iovec(wdata, from, &cur_len, &num_pages);
bytes = min_t(size_t, cur_len, PAGE_SIZE); if (rc) {
copied = copy_page_from_iter(wdata->pages[i], 0, bytes,
from);
cur_len -= copied;
/*
* If we didn't copy as much as we expected, then that
* may mean we trod into an unmapped area. Stop copying
* at that point. On the next pass through the big
* loop, we'll likely end up getting a zero-length
* write and bailing out of it.
*/
if (copied < bytes)
break;
}
cur_len = save_len - cur_len;
/*
* If we have no data to send, then that probably means that
* the copy above failed altogether. That's most likely because
* the address in the iovec was bogus. Set the rc to -EFAULT,
* free anything we allocated and bail out.
*/
if (!cur_len) {
for (i = 0; i < nr_pages; i++) for (i = 0; i < nr_pages; i++)
put_page(wdata->pages[i]); put_page(wdata->pages[i]);
kfree(wdata); kfree(wdata);
rc = -EFAULT; add_credits_and_wake_if(server, credits, 0);
break; break;
} }
/* /*
* i + 1 now represents the number of pages we actually used in * Bring nr_pages down to the number of pages we actually used,
* the copy phase above. Bring nr_pages down to that, and free * and free any pages that we didn't use.
* any pages that we didn't use.
*/ */
for ( ; nr_pages > i + 1; nr_pages--) for ( ; nr_pages > num_pages; nr_pages--)
put_page(wdata->pages[nr_pages - 1]); put_page(wdata->pages[nr_pages - 1]);
wdata->sync_mode = WB_SYNC_ALL; wdata->sync_mode = WB_SYNC_ALL;
...@@ -2489,18 +2526,69 @@ cifs_iovec_write(struct file *file, struct iov_iter *from, loff_t *poffset) ...@@ -2489,18 +2526,69 @@ cifs_iovec_write(struct file *file, struct iov_iter *from, loff_t *poffset)
wdata->bytes = cur_len; wdata->bytes = cur_len;
wdata->pagesz = PAGE_SIZE; wdata->pagesz = PAGE_SIZE;
wdata->tailsz = cur_len - ((nr_pages - 1) * PAGE_SIZE); wdata->tailsz = cur_len - ((nr_pages - 1) * PAGE_SIZE);
rc = cifs_uncached_retry_writev(wdata); wdata->credits = credits;
if (!wdata->cfile->invalidHandle ||
!cifs_reopen_file(wdata->cfile, false))
rc = server->ops->async_writev(wdata,
cifs_uncached_writedata_release);
if (rc) { if (rc) {
add_credits_and_wake_if(server, wdata->credits, 0);
kref_put(&wdata->refcount, kref_put(&wdata->refcount,
cifs_uncached_writedata_release); cifs_uncached_writedata_release);
if (rc == -EAGAIN) {
memcpy(from, &saved_from,
sizeof(struct iov_iter));
iov_iter_advance(from, offset - saved_offset);
continue;
}
break; break;
} }
list_add_tail(&wdata->list, &wdata_list); list_add_tail(&wdata->list, wdata_list);
offset += cur_len; offset += cur_len;
len -= cur_len; len -= cur_len;
} while (len > 0); } while (len > 0);
return rc;
}
static ssize_t
cifs_iovec_write(struct file *file, struct iov_iter *from, loff_t *poffset)
{
size_t len;
ssize_t total_written = 0;
struct cifsFileInfo *open_file;
struct cifs_tcon *tcon;
struct cifs_sb_info *cifs_sb;
struct cifs_writedata *wdata, *tmp;
struct list_head wdata_list;
struct iov_iter saved_from;
int rc;
len = iov_iter_count(from);
rc = generic_write_checks(file, poffset, &len, 0);
if (rc)
return rc;
if (!len)
return 0;
iov_iter_truncate(from, len);
INIT_LIST_HEAD(&wdata_list);
cifs_sb = CIFS_SB(file->f_path.dentry->d_sb);
open_file = file->private_data;
tcon = tlink_tcon(open_file->tlink);
if (!tcon->ses->server->ops->async_writev)
return -ENOSYS;
memcpy(&saved_from, from, sizeof(struct iov_iter));
rc = cifs_write_from_iter(*poffset, len, from, open_file, cifs_sb,
&wdata_list);
/* /*
* If at least one write was successfully sent, then discard any rc * If at least one write was successfully sent, then discard any rc
* value from the later writes. If the other write succeeds, then * value from the later writes. If the other write succeeds, then
...@@ -2529,7 +2617,25 @@ cifs_iovec_write(struct file *file, struct iov_iter *from, loff_t *poffset) ...@@ -2529,7 +2617,25 @@ cifs_iovec_write(struct file *file, struct iov_iter *from, loff_t *poffset)
/* resend call if it's a retryable error */ /* resend call if it's a retryable error */
if (rc == -EAGAIN) { if (rc == -EAGAIN) {
rc = cifs_uncached_retry_writev(wdata); struct list_head tmp_list;
struct iov_iter tmp_from;
INIT_LIST_HEAD(&tmp_list);
list_del_init(&wdata->list);
memcpy(&tmp_from, &saved_from,
sizeof(struct iov_iter));
iov_iter_advance(&tmp_from,
wdata->offset - *poffset);
rc = cifs_write_from_iter(wdata->offset,
wdata->bytes, &tmp_from,
open_file, cifs_sb, &tmp_list);
list_splice(&tmp_list, &wdata_list);
kref_put(&wdata->refcount,
cifs_uncached_writedata_release);
goto restart_loop; goto restart_loop;
} }
} }
...@@ -2722,26 +2828,6 @@ cifs_uncached_readdata_release(struct kref *refcount) ...@@ -2722,26 +2828,6 @@ cifs_uncached_readdata_release(struct kref *refcount)
cifs_readdata_release(refcount); cifs_readdata_release(refcount);
} }
static int
cifs_retry_async_readv(struct cifs_readdata *rdata)
{
int rc;
struct TCP_Server_Info *server;
server = tlink_tcon(rdata->cfile->tlink)->ses->server;
do {
if (rdata->cfile->invalidHandle) {
rc = cifs_reopen_file(rdata->cfile, true);
if (rc != 0)
continue;
}
rc = server->ops->async_readv(rdata);
} while (rc == -EAGAIN);
return rc;
}
/** /**
* cifs_readdata_to_iov - copy data from pages in response to an iovec * cifs_readdata_to_iov - copy data from pages in response to an iovec
* @rdata: the readdata response with list of pages holding data * @rdata: the readdata response with list of pages holding data
...@@ -2754,7 +2840,7 @@ cifs_retry_async_readv(struct cifs_readdata *rdata) ...@@ -2754,7 +2840,7 @@ cifs_retry_async_readv(struct cifs_readdata *rdata)
static int static int
cifs_readdata_to_iov(struct cifs_readdata *rdata, struct iov_iter *iter) cifs_readdata_to_iov(struct cifs_readdata *rdata, struct iov_iter *iter)
{ {
size_t remaining = rdata->bytes; size_t remaining = rdata->got_bytes;
unsigned int i; unsigned int i;
for (i = 0; i < rdata->nr_pages; i++) { for (i = 0; i < rdata->nr_pages; i++) {
...@@ -2782,11 +2868,12 @@ static int ...@@ -2782,11 +2868,12 @@ static int
cifs_uncached_read_into_pages(struct TCP_Server_Info *server, cifs_uncached_read_into_pages(struct TCP_Server_Info *server,
struct cifs_readdata *rdata, unsigned int len) struct cifs_readdata *rdata, unsigned int len)
{ {
int total_read = 0, result = 0; int result = 0;
unsigned int i; unsigned int i;
unsigned int nr_pages = rdata->nr_pages; unsigned int nr_pages = rdata->nr_pages;
struct kvec iov; struct kvec iov;
rdata->got_bytes = 0;
rdata->tailsz = PAGE_SIZE; rdata->tailsz = PAGE_SIZE;
for (i = 0; i < nr_pages; i++) { for (i = 0; i < nr_pages; i++) {
struct page *page = rdata->pages[i]; struct page *page = rdata->pages[i];
...@@ -2820,55 +2907,45 @@ cifs_uncached_read_into_pages(struct TCP_Server_Info *server, ...@@ -2820,55 +2907,45 @@ cifs_uncached_read_into_pages(struct TCP_Server_Info *server,
if (result < 0) if (result < 0)
break; break;
total_read += result; rdata->got_bytes += result;
} }
return total_read > 0 ? total_read : result; return rdata->got_bytes > 0 && result != -ECONNABORTED ?
rdata->got_bytes : result;
} }
ssize_t cifs_user_readv(struct kiocb *iocb, struct iov_iter *to) static int
cifs_send_async_read(loff_t offset, size_t len, struct cifsFileInfo *open_file,
struct cifs_sb_info *cifs_sb, struct list_head *rdata_list)
{ {
struct file *file = iocb->ki_filp; struct cifs_readdata *rdata;
ssize_t rc; unsigned int npages, rsize, credits;
size_t len, cur_len; size_t cur_len;
ssize_t total_read = 0; int rc;
loff_t offset = iocb->ki_pos;
unsigned int npages;
struct cifs_sb_info *cifs_sb;
struct cifs_tcon *tcon;
struct cifsFileInfo *open_file;
struct cifs_readdata *rdata, *tmp;
struct list_head rdata_list;
pid_t pid; pid_t pid;
struct TCP_Server_Info *server;
len = iov_iter_count(to); server = tlink_tcon(open_file->tlink)->ses->server;
if (!len)
return 0;
INIT_LIST_HEAD(&rdata_list);
cifs_sb = CIFS_SB(file->f_path.dentry->d_sb);
open_file = file->private_data;
tcon = tlink_tcon(open_file->tlink);
if (!tcon->ses->server->ops->async_readv)
return -ENOSYS;
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_RWPIDFORWARD) if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_RWPIDFORWARD)
pid = open_file->pid; pid = open_file->pid;
else else
pid = current->tgid; pid = current->tgid;
if ((file->f_flags & O_ACCMODE) == O_WRONLY)
cifs_dbg(FYI, "attempting read on write only file instance\n");
do { do {
cur_len = min_t(const size_t, len - total_read, cifs_sb->rsize); rc = server->ops->wait_mtu_credits(server, cifs_sb->rsize,
&rsize, &credits);
if (rc)
break;
cur_len = min_t(const size_t, len, rsize);
npages = DIV_ROUND_UP(cur_len, PAGE_SIZE); npages = DIV_ROUND_UP(cur_len, PAGE_SIZE);
/* allocate a readdata struct */ /* allocate a readdata struct */
rdata = cifs_readdata_alloc(npages, rdata = cifs_readdata_alloc(npages,
cifs_uncached_readv_complete); cifs_uncached_readv_complete);
if (!rdata) { if (!rdata) {
add_credits_and_wake_if(server, credits, 0);
rc = -ENOMEM; rc = -ENOMEM;
break; break;
} }
...@@ -2884,44 +2961,113 @@ ssize_t cifs_user_readv(struct kiocb *iocb, struct iov_iter *to) ...@@ -2884,44 +2961,113 @@ ssize_t cifs_user_readv(struct kiocb *iocb, struct iov_iter *to)
rdata->pid = pid; rdata->pid = pid;
rdata->pagesz = PAGE_SIZE; rdata->pagesz = PAGE_SIZE;
rdata->read_into_pages = cifs_uncached_read_into_pages; rdata->read_into_pages = cifs_uncached_read_into_pages;
rdata->credits = credits;
rc = cifs_retry_async_readv(rdata); if (!rdata->cfile->invalidHandle ||
!cifs_reopen_file(rdata->cfile, true))
rc = server->ops->async_readv(rdata);
error: error:
if (rc) { if (rc) {
add_credits_and_wake_if(server, rdata->credits, 0);
kref_put(&rdata->refcount, kref_put(&rdata->refcount,
cifs_uncached_readdata_release); cifs_uncached_readdata_release);
if (rc == -EAGAIN)
continue;
break; break;
} }
list_add_tail(&rdata->list, &rdata_list); list_add_tail(&rdata->list, rdata_list);
offset += cur_len; offset += cur_len;
len -= cur_len; len -= cur_len;
} while (len > 0); } while (len > 0);
return rc;
}
ssize_t cifs_user_readv(struct kiocb *iocb, struct iov_iter *to)
{
struct file *file = iocb->ki_filp;
ssize_t rc;
size_t len;
ssize_t total_read = 0;
loff_t offset = iocb->ki_pos;
struct cifs_sb_info *cifs_sb;
struct cifs_tcon *tcon;
struct cifsFileInfo *open_file;
struct cifs_readdata *rdata, *tmp;
struct list_head rdata_list;
len = iov_iter_count(to);
if (!len)
return 0;
INIT_LIST_HEAD(&rdata_list);
cifs_sb = CIFS_SB(file->f_path.dentry->d_sb);
open_file = file->private_data;
tcon = tlink_tcon(open_file->tlink);
if (!tcon->ses->server->ops->async_readv)
return -ENOSYS;
if ((file->f_flags & O_ACCMODE) == O_WRONLY)
cifs_dbg(FYI, "attempting read on write only file instance\n");
rc = cifs_send_async_read(offset, len, open_file, cifs_sb, &rdata_list);
/* if at least one read request send succeeded, then reset rc */ /* if at least one read request send succeeded, then reset rc */
if (!list_empty(&rdata_list)) if (!list_empty(&rdata_list))
rc = 0; rc = 0;
len = iov_iter_count(to); len = iov_iter_count(to);
/* the loop below should proceed in the order of increasing offsets */ /* the loop below should proceed in the order of increasing offsets */
again:
list_for_each_entry_safe(rdata, tmp, &rdata_list, list) { list_for_each_entry_safe(rdata, tmp, &rdata_list, list) {
again:
if (!rc) { if (!rc) {
/* FIXME: freezable sleep too? */ /* FIXME: freezable sleep too? */
rc = wait_for_completion_killable(&rdata->done); rc = wait_for_completion_killable(&rdata->done);
if (rc) if (rc)
rc = -EINTR; rc = -EINTR;
else if (rdata->result) { else if (rdata->result == -EAGAIN) {
rc = rdata->result;
/* resend call if it's a retryable error */ /* resend call if it's a retryable error */
if (rc == -EAGAIN) { struct list_head tmp_list;
rc = cifs_retry_async_readv(rdata); unsigned int got_bytes = rdata->got_bytes;
goto again;
} list_del_init(&rdata->list);
} else { INIT_LIST_HEAD(&tmp_list);
/*
* Got a part of data and then reconnect has
* happened -- fill the buffer and continue
* reading.
*/
if (got_bytes && got_bytes < rdata->bytes) {
rc = cifs_readdata_to_iov(rdata, to); rc = cifs_readdata_to_iov(rdata, to);
if (rc) {
kref_put(&rdata->refcount,
cifs_uncached_readdata_release);
continue;
} }
}
rc = cifs_send_async_read(
rdata->offset + got_bytes,
rdata->bytes - got_bytes,
rdata->cfile, cifs_sb,
&tmp_list);
list_splice(&tmp_list, &rdata_list);
kref_put(&rdata->refcount,
cifs_uncached_readdata_release);
goto again;
} else if (rdata->result)
rc = rdata->result;
else
rc = cifs_readdata_to_iov(rdata, to);
/* if there was a short read -- discard anything left */
if (rdata->got_bytes && rdata->got_bytes < rdata->bytes)
rc = -ENODATA;
} }
list_del_init(&rdata->list); list_del_init(&rdata->list);
kref_put(&rdata->refcount, cifs_uncached_readdata_release); kref_put(&rdata->refcount, cifs_uncached_readdata_release);
...@@ -3030,18 +3176,19 @@ cifs_read(struct file *file, char *read_data, size_t read_size, loff_t *offset) ...@@ -3030,18 +3176,19 @@ cifs_read(struct file *file, char *read_data, size_t read_size, loff_t *offset)
for (total_read = 0, cur_offset = read_data; read_size > total_read; for (total_read = 0, cur_offset = read_data; read_size > total_read;
total_read += bytes_read, cur_offset += bytes_read) { total_read += bytes_read, cur_offset += bytes_read) {
current_read_size = min_t(uint, read_size - total_read, rsize); do {
current_read_size = min_t(uint, read_size - total_read,
rsize);
/* /*
* For windows me and 9x we do not want to request more than it * For windows me and 9x we do not want to request more
* negotiated since it will refuse the read then. * than it negotiated since it will refuse the read
* then.
*/ */
if ((tcon->ses) && !(tcon->ses->capabilities & if ((tcon->ses) && !(tcon->ses->capabilities &
tcon->ses->server->vals->cap_large_files)) { tcon->ses->server->vals->cap_large_files)) {
current_read_size = min_t(uint, current_read_size, current_read_size = min_t(uint,
CIFSMaxBufSize); current_read_size, CIFSMaxBufSize);
} }
rc = -EAGAIN;
while (rc == -EAGAIN) {
if (open_file->invalidHandle) { if (open_file->invalidHandle) {
rc = cifs_reopen_file(open_file, true); rc = cifs_reopen_file(open_file, true);
if (rc != 0) if (rc != 0)
...@@ -3054,7 +3201,8 @@ cifs_read(struct file *file, char *read_data, size_t read_size, loff_t *offset) ...@@ -3054,7 +3201,8 @@ cifs_read(struct file *file, char *read_data, size_t read_size, loff_t *offset)
rc = server->ops->sync_read(xid, open_file, &io_parms, rc = server->ops->sync_read(xid, open_file, &io_parms,
&bytes_read, &cur_offset, &bytes_read, &cur_offset,
&buf_type); &buf_type);
} } while (rc == -EAGAIN);
if (rc || (bytes_read == 0)) { if (rc || (bytes_read == 0)) {
if (total_read) { if (total_read) {
break; break;
...@@ -3133,25 +3281,30 @@ int cifs_file_mmap(struct file *file, struct vm_area_struct *vma) ...@@ -3133,25 +3281,30 @@ int cifs_file_mmap(struct file *file, struct vm_area_struct *vma)
static void static void
cifs_readv_complete(struct work_struct *work) cifs_readv_complete(struct work_struct *work)
{ {
unsigned int i; unsigned int i, got_bytes;
struct cifs_readdata *rdata = container_of(work, struct cifs_readdata *rdata = container_of(work,
struct cifs_readdata, work); struct cifs_readdata, work);
got_bytes = rdata->got_bytes;
for (i = 0; i < rdata->nr_pages; i++) { for (i = 0; i < rdata->nr_pages; i++) {
struct page *page = rdata->pages[i]; struct page *page = rdata->pages[i];
lru_cache_add_file(page); lru_cache_add_file(page);
if (rdata->result == 0) { if (rdata->result == 0 ||
(rdata->result == -EAGAIN && got_bytes)) {
flush_dcache_page(page); flush_dcache_page(page);
SetPageUptodate(page); SetPageUptodate(page);
} }
unlock_page(page); unlock_page(page);
if (rdata->result == 0) if (rdata->result == 0 ||
(rdata->result == -EAGAIN && got_bytes))
cifs_readpage_to_fscache(rdata->mapping->host, page); cifs_readpage_to_fscache(rdata->mapping->host, page);
got_bytes -= min_t(unsigned int, PAGE_CACHE_SIZE, got_bytes);
page_cache_release(page); page_cache_release(page);
rdata->pages[i] = NULL; rdata->pages[i] = NULL;
} }
...@@ -3162,7 +3315,7 @@ static int ...@@ -3162,7 +3315,7 @@ static int
cifs_readpages_read_into_pages(struct TCP_Server_Info *server, cifs_readpages_read_into_pages(struct TCP_Server_Info *server,
struct cifs_readdata *rdata, unsigned int len) struct cifs_readdata *rdata, unsigned int len)
{ {
int total_read = 0, result = 0; int result = 0;
unsigned int i; unsigned int i;
u64 eof; u64 eof;
pgoff_t eof_index; pgoff_t eof_index;
...@@ -3174,6 +3327,7 @@ cifs_readpages_read_into_pages(struct TCP_Server_Info *server, ...@@ -3174,6 +3327,7 @@ cifs_readpages_read_into_pages(struct TCP_Server_Info *server,
eof_index = eof ? (eof - 1) >> PAGE_CACHE_SHIFT : 0; eof_index = eof ? (eof - 1) >> PAGE_CACHE_SHIFT : 0;
cifs_dbg(FYI, "eof=%llu eof_index=%lu\n", eof, eof_index); cifs_dbg(FYI, "eof=%llu eof_index=%lu\n", eof, eof_index);
rdata->got_bytes = 0;
rdata->tailsz = PAGE_CACHE_SIZE; rdata->tailsz = PAGE_CACHE_SIZE;
for (i = 0; i < nr_pages; i++) { for (i = 0; i < nr_pages; i++) {
struct page *page = rdata->pages[i]; struct page *page = rdata->pages[i];
...@@ -3228,10 +3382,70 @@ cifs_readpages_read_into_pages(struct TCP_Server_Info *server, ...@@ -3228,10 +3382,70 @@ cifs_readpages_read_into_pages(struct TCP_Server_Info *server,
if (result < 0) if (result < 0)
break; break;
total_read += result; rdata->got_bytes += result;
} }
return total_read > 0 ? total_read : result; return rdata->got_bytes > 0 && result != -ECONNABORTED ?
rdata->got_bytes : result;
}
static int
readpages_get_pages(struct address_space *mapping, struct list_head *page_list,
unsigned int rsize, struct list_head *tmplist,
unsigned int *nr_pages, loff_t *offset, unsigned int *bytes)
{
struct page *page, *tpage;
unsigned int expected_index;
int rc;
INIT_LIST_HEAD(tmplist);
page = list_entry(page_list->prev, struct page, lru);
/*
* Lock the page and put it in the cache. Since no one else
* should have access to this page, we're safe to simply set
* PG_locked without checking it first.
*/
__set_page_locked(page);
rc = add_to_page_cache_locked(page, mapping,
page->index, GFP_KERNEL);
/* give up if we can't stick it in the cache */
if (rc) {
__clear_page_locked(page);
return rc;
}
/* move first page to the tmplist */
*offset = (loff_t)page->index << PAGE_CACHE_SHIFT;
*bytes = PAGE_CACHE_SIZE;
*nr_pages = 1;
list_move_tail(&page->lru, tmplist);
/* now try and add more pages onto the request */
expected_index = page->index + 1;
list_for_each_entry_safe_reverse(page, tpage, page_list, lru) {
/* discontinuity ? */
if (page->index != expected_index)
break;
/* would this page push the read over the rsize? */
if (*bytes + PAGE_CACHE_SIZE > rsize)
break;
__set_page_locked(page);
if (add_to_page_cache_locked(page, mapping, page->index,
GFP_KERNEL)) {
__clear_page_locked(page);
break;
}
list_move_tail(&page->lru, tmplist);
(*bytes) += PAGE_CACHE_SIZE;
expected_index++;
(*nr_pages)++;
}
return rc;
} }
static int cifs_readpages(struct file *file, struct address_space *mapping, static int cifs_readpages(struct file *file, struct address_space *mapping,
...@@ -3241,18 +3455,9 @@ static int cifs_readpages(struct file *file, struct address_space *mapping, ...@@ -3241,18 +3455,9 @@ static int cifs_readpages(struct file *file, struct address_space *mapping,
struct list_head tmplist; struct list_head tmplist;
struct cifsFileInfo *open_file = file->private_data; struct cifsFileInfo *open_file = file->private_data;
struct cifs_sb_info *cifs_sb = CIFS_SB(file->f_path.dentry->d_sb); struct cifs_sb_info *cifs_sb = CIFS_SB(file->f_path.dentry->d_sb);
unsigned int rsize = cifs_sb->rsize; struct TCP_Server_Info *server;
pid_t pid; pid_t pid;
/*
* Give up immediately if rsize is too small to read an entire page.
* The VFS will fall back to readpage. We should never reach this
* point however since we set ra_pages to 0 when the rsize is smaller
* than a cache page.
*/
if (unlikely(rsize < PAGE_CACHE_SIZE))
return 0;
/* /*
* Reads as many pages as possible from fscache. Returns -ENOBUFS * Reads as many pages as possible from fscache. Returns -ENOBUFS
* immediately if the cookie is negative * immediately if the cookie is negative
...@@ -3271,7 +3476,7 @@ static int cifs_readpages(struct file *file, struct address_space *mapping, ...@@ -3271,7 +3476,7 @@ static int cifs_readpages(struct file *file, struct address_space *mapping,
pid = current->tgid; pid = current->tgid;
rc = 0; rc = 0;
INIT_LIST_HEAD(&tmplist); server = tlink_tcon(open_file->tlink)->ses->server;
cifs_dbg(FYI, "%s: file=%p mapping=%p num_pages=%u\n", cifs_dbg(FYI, "%s: file=%p mapping=%p num_pages=%u\n",
__func__, file, mapping, num_pages); __func__, file, mapping, num_pages);
...@@ -3288,57 +3493,34 @@ static int cifs_readpages(struct file *file, struct address_space *mapping, ...@@ -3288,57 +3493,34 @@ static int cifs_readpages(struct file *file, struct address_space *mapping,
* the rdata->pages, then we want them in increasing order. * the rdata->pages, then we want them in increasing order.
*/ */
while (!list_empty(page_list)) { while (!list_empty(page_list)) {
unsigned int i; unsigned int i, nr_pages, bytes, rsize;
unsigned int bytes = PAGE_CACHE_SIZE;
unsigned int expected_index;
unsigned int nr_pages = 1;
loff_t offset; loff_t offset;
struct page *page, *tpage; struct page *page, *tpage;
struct cifs_readdata *rdata; struct cifs_readdata *rdata;
unsigned credits;
page = list_entry(page_list->prev, struct page, lru); rc = server->ops->wait_mtu_credits(server, cifs_sb->rsize,
&rsize, &credits);
if (rc)
break;
/* /*
* Lock the page and put it in the cache. Since no one else * Give up immediately if rsize is too small to read an entire
* should have access to this page, we're safe to simply set * page. The VFS will fall back to readpage. We should never
* PG_locked without checking it first. * reach this point however since we set ra_pages to 0 when the
* rsize is smaller than a cache page.
*/ */
__set_page_locked(page); if (unlikely(rsize < PAGE_CACHE_SIZE)) {
rc = add_to_page_cache_locked(page, mapping, add_credits_and_wake_if(server, credits, 0);
page->index, GFP_KERNEL); return 0;
/* give up if we can't stick it in the cache */
if (rc) {
__clear_page_locked(page);
break;
} }
/* move first page to the tmplist */ rc = readpages_get_pages(mapping, page_list, rsize, &tmplist,
offset = (loff_t)page->index << PAGE_CACHE_SHIFT; &nr_pages, &offset, &bytes);
list_move_tail(&page->lru, &tmplist); if (rc) {
add_credits_and_wake_if(server, credits, 0);
/* now try and add more pages onto the request */
expected_index = page->index + 1;
list_for_each_entry_safe_reverse(page, tpage, page_list, lru) {
/* discontinuity ? */
if (page->index != expected_index)
break;
/* would this page push the read over the rsize? */
if (bytes + PAGE_CACHE_SIZE > rsize)
break;
__set_page_locked(page);
if (add_to_page_cache_locked(page, mapping,
page->index, GFP_KERNEL)) {
__clear_page_locked(page);
break; break;
} }
list_move_tail(&page->lru, &tmplist);
bytes += PAGE_CACHE_SIZE;
expected_index++;
nr_pages++;
}
rdata = cifs_readdata_alloc(nr_pages, cifs_readv_complete); rdata = cifs_readdata_alloc(nr_pages, cifs_readv_complete);
if (!rdata) { if (!rdata) {
...@@ -3350,6 +3532,7 @@ static int cifs_readpages(struct file *file, struct address_space *mapping, ...@@ -3350,6 +3532,7 @@ static int cifs_readpages(struct file *file, struct address_space *mapping,
page_cache_release(page); page_cache_release(page);
} }
rc = -ENOMEM; rc = -ENOMEM;
add_credits_and_wake_if(server, credits, 0);
break; break;
} }
...@@ -3360,21 +3543,32 @@ static int cifs_readpages(struct file *file, struct address_space *mapping, ...@@ -3360,21 +3543,32 @@ static int cifs_readpages(struct file *file, struct address_space *mapping,
rdata->pid = pid; rdata->pid = pid;
rdata->pagesz = PAGE_CACHE_SIZE; rdata->pagesz = PAGE_CACHE_SIZE;
rdata->read_into_pages = cifs_readpages_read_into_pages; rdata->read_into_pages = cifs_readpages_read_into_pages;
rdata->credits = credits;
list_for_each_entry_safe(page, tpage, &tmplist, lru) { list_for_each_entry_safe(page, tpage, &tmplist, lru) {
list_del(&page->lru); list_del(&page->lru);
rdata->pages[rdata->nr_pages++] = page; rdata->pages[rdata->nr_pages++] = page;
} }
rc = cifs_retry_async_readv(rdata); if (!rdata->cfile->invalidHandle ||
if (rc != 0) { !cifs_reopen_file(rdata->cfile, true))
rc = server->ops->async_readv(rdata);
if (rc) {
add_credits_and_wake_if(server, rdata->credits, 0);
for (i = 0; i < rdata->nr_pages; i++) { for (i = 0; i < rdata->nr_pages; i++) {
page = rdata->pages[i]; page = rdata->pages[i];
lru_cache_add_file(page); lru_cache_add_file(page);
unlock_page(page); unlock_page(page);
page_cache_release(page); page_cache_release(page);
if (rc == -EAGAIN)
list_add_tail(&page->lru, &tmplist);
} }
kref_put(&rdata->refcount, cifs_readdata_release); kref_put(&rdata->refcount, cifs_readdata_release);
if (rc == -EAGAIN) {
/* Re-add pages to the page_list and retry */
list_splice(&tmplist, page_list);
continue;
}
break; break;
} }
......
...@@ -226,6 +226,15 @@ cifs_small_buf_release(void *buf_to_free) ...@@ -226,6 +226,15 @@ cifs_small_buf_release(void *buf_to_free)
return; return;
} }
void
free_rsp_buf(int resp_buftype, void *rsp)
{
if (resp_buftype == CIFS_SMALL_BUFFER)
cifs_small_buf_release(rsp);
else if (resp_buftype == CIFS_LARGE_BUFFER)
cifs_buf_release(rsp);
}
/* NB: MID can not be set if treeCon not passed in, in that /* NB: MID can not be set if treeCon not passed in, in that
case it is responsbility of caller to set the mid */ case it is responsbility of caller to set the mid */
void void
...@@ -414,7 +423,7 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv) ...@@ -414,7 +423,7 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv)
return true; return true;
} }
if (pSMBr->hdr.Status.CifsError) { if (pSMBr->hdr.Status.CifsError) {
cifs_dbg(FYI, "notify err 0x%d\n", cifs_dbg(FYI, "notify err 0x%x\n",
pSMBr->hdr.Status.CifsError); pSMBr->hdr.Status.CifsError);
return true; return true;
} }
...@@ -441,7 +450,7 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv) ...@@ -441,7 +450,7 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv)
if (pSMB->hdr.WordCount != 8) if (pSMB->hdr.WordCount != 8)
return false; return false;
cifs_dbg(FYI, "oplock type 0x%d level 0x%d\n", cifs_dbg(FYI, "oplock type 0x%x level 0x%x\n",
pSMB->LockType, pSMB->OplockLevel); pSMB->LockType, pSMB->OplockLevel);
if (!(pSMB->LockType & LOCKING_ANDX_OPLOCK_RELEASE)) if (!(pSMB->LockType & LOCKING_ANDX_OPLOCK_RELEASE))
return false; return false;
......
...@@ -520,112 +520,156 @@ select_sectype(struct TCP_Server_Info *server, enum securityEnum requested) ...@@ -520,112 +520,156 @@ select_sectype(struct TCP_Server_Info *server, enum securityEnum requested)
} }
} }
int struct sess_data {
CIFS_SessSetup(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 sess_data *);
int result;
/* 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[3];
};
static int
sess_alloc_buffer(struct sess_data *sess_data, int wct)
{ {
int rc = 0; int rc;
int wct; struct cifs_ses *ses = sess_data->ses;
struct smb_hdr *smb_buf; struct smb_hdr *smb_buf;
char *bcc_ptr;
char *str_area;
SESSION_SETUP_ANDX *pSMB;
__u32 capabilities;
__u16 count;
int resp_buf_type;
struct kvec iov[3];
enum securityEnum type;
__u16 action, bytes_remaining;
struct key *spnego_key = NULL;
__le32 phase = NtLmNegotiate; /* NTLMSSP, if needed, is multistage */
u16 blob_len;
char *ntlmsspblob = NULL;
if (ses == NULL) { rc = small_smb_init_no_tc(SMB_COM_SESSION_SETUP_ANDX, wct, ses,
WARN(1, "%s: ses == NULL!", __func__); (void **)&smb_buf);
return -EINVAL;
}
type = select_sectype(ses->server, ses->sectype); if (rc)
cifs_dbg(FYI, "sess setup type %d\n", type); return rc;
if (type == Unspecified) {
cifs_dbg(VFS,
"Unable to select appropriate authentication method!");
return -EINVAL;
}
if (type == RawNTLMSSP) { sess_data->iov[0].iov_base = (char *)smb_buf;
/* if memory allocation is successful, caller of this function sess_data->iov[0].iov_len = be32_to_cpu(smb_buf->smb_buf_length) + 4;
* frees it. /*
* This variable will be used to clear the buffer
* allocated above in case of any error in the calling function.
*/ */
ses->ntlmssp = kmalloc(sizeof(struct ntlmssp_auth), GFP_KERNEL); sess_data->buf0_type = CIFS_SMALL_BUFFER;
if (!ses->ntlmssp)
return -ENOMEM;
ses->ntlmssp->sesskey_per_smbsess = false;
/* 2000 big enough to fit max user, domain, NOS name etc. */
sess_data->iov[2].iov_base = kmalloc(2000, GFP_KERNEL);
if (!sess_data->iov[2].iov_base) {
rc = -ENOMEM;
goto out_free_smb_buf;
} }
ssetup_ntlmssp_authenticate: return 0;
if (phase == NtLmChallenge)
phase = NtLmAuthenticate; /* if ntlmssp, now final phase */
if (type == LANMAN) {
#ifndef CONFIG_CIFS_WEAK_PW_HASH
/* LANMAN and plaintext are less secure and off by default.
So we make this explicitly be turned on in kconfig (in the
build) and turned on at runtime (changed from the default)
in proc/fs/cifs or via mount parm. Unfortunately this is
needed for old Win (e.g. Win95), some obscure NAS and OS/2 */
return -EOPNOTSUPP;
#endif
wct = 10; /* lanman 2 style sessionsetup */
} else if ((type == NTLM) || (type == NTLMv2)) {
/* For NTLMv2 failures eventually may need to retry NTLM */
wct = 13; /* old style NTLM sessionsetup */
} else /* same size: negotiate or auth, NTLMSSP or extended security */
wct = 12;
rc = small_smb_init_no_tc(SMB_COM_SESSION_SETUP_ANDX, wct, ses, out_free_smb_buf:
(void **)&smb_buf); kfree(smb_buf);
if (rc) sess_data->iov[0].iov_base = NULL;
sess_data->iov[0].iov_len = 0;
sess_data->buf0_type = CIFS_NO_BUFFER;
return rc; return rc;
}
pSMB = (SESSION_SETUP_ANDX *)smb_buf; static void
sess_free_buffer(struct sess_data *sess_data)
{
capabilities = cifs_ssetup_hdr(ses, pSMB); free_rsp_buf(sess_data->buf0_type, sess_data->iov[0].iov_base);
sess_data->buf0_type = CIFS_NO_BUFFER;
kfree(sess_data->iov[2].iov_base);
}
/* we will send the SMB in three pieces: static int
a fixed length beginning part, an optional sess_establish_session(struct sess_data *sess_data)
SPNEGO blob (which can be zero length), and a {
last part which will include the strings struct cifs_ses *ses = sess_data->ses;
and rest of bcc area. This allows us to avoid
a large buffer 17K allocation */
iov[0].iov_base = (char *)pSMB;
iov[0].iov_len = be32_to_cpu(smb_buf->smb_buf_length) + 4;
/* setting this here allows the code at the end of the function
to free the request buffer if there's an error */
resp_buf_type = CIFS_SMALL_BUFFER;
/* 2000 big enough to fit max user, domain, NOS name etc. */ mutex_lock(&ses->server->srv_mutex);
str_area = kmalloc(2000, GFP_KERNEL); if (!ses->server->session_estab) {
if (str_area == NULL) { if (ses->server->sign) {
rc = -ENOMEM; ses->server->session_key.response =
goto ssetup_exit; kmemdup(ses->auth_key.response,
ses->auth_key.len, GFP_KERNEL);
if (!ses->server->session_key.response) {
mutex_unlock(&ses->server->srv_mutex);
return -ENOMEM;
} }
bcc_ptr = str_area; ses->server->session_key.len =
ses->auth_key.len;
}
ses->server->sequence_number = 0x2;
ses->server->session_estab = true;
}
mutex_unlock(&ses->server->srv_mutex);
cifs_dbg(FYI, "CIFS session established successfully\n");
spin_lock(&GlobalMid_Lock);
ses->status = CifsGood;
ses->need_reconnect = false;
spin_unlock(&GlobalMid_Lock);
return 0;
}
iov[1].iov_base = NULL; static int
iov[1].iov_len = 0; sess_sendreceive(struct sess_data *sess_data)
{
int rc;
struct smb_hdr *smb_buf = (struct smb_hdr *) sess_data->iov[0].iov_base;
__u16 count;
count = sess_data->iov[1].iov_len + sess_data->iov[2].iov_len;
smb_buf->smb_buf_length =
cpu_to_be32(be32_to_cpu(smb_buf->smb_buf_length) + count);
put_bcc(count, smb_buf);
rc = SendReceive2(sess_data->xid, sess_data->ses,
sess_data->iov, 3 /* num_iovecs */,
&sess_data->buf0_type,
CIFS_LOG_ERROR);
return rc;
}
if (type == LANMAN) { /*
* LANMAN and plaintext are less secure and off by default.
* So we make this explicitly be turned on in kconfig (in the
* build) and turned on at runtime (changed from the default)
* in proc/fs/cifs or via mount parm. Unfortunately this is
* needed for old Win (e.g. Win95), some obscure NAS and OS/2
*/
#ifdef CONFIG_CIFS_WEAK_PW_HASH #ifdef CONFIG_CIFS_WEAK_PW_HASH
static void
sess_auth_lanman(struct sess_data *sess_data)
{
int rc = 0;
struct smb_hdr *smb_buf;
SESSION_SETUP_ANDX *pSMB;
char *bcc_ptr;
struct cifs_ses *ses = sess_data->ses;
char lnm_session_key[CIFS_AUTH_RESP_SIZE]; char lnm_session_key[CIFS_AUTH_RESP_SIZE];
__u32 capabilities;
__u16 bytes_remaining;
/* lanman 2 style sessionsetup */
/* wct = 10 */
rc = sess_alloc_buffer(sess_data, 10);
if (rc)
goto out;
pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
bcc_ptr = sess_data->iov[2].iov_base;
capabilities = cifs_ssetup_hdr(ses, pSMB);
pSMB->req.hdr.Flags2 &= ~SMBFLG2_UNICODE; pSMB->req.hdr.Flags2 &= ~SMBFLG2_UNICODE;
/* no capabilities flags in old lanman negotiation */ /* no capabilities flags in old lanman negotiation */
pSMB->old_req.PasswordLength = cpu_to_le16(CIFS_AUTH_RESP_SIZE); pSMB->old_req.PasswordLength = cpu_to_le16(CIFS_AUTH_RESP_SIZE);
/* Calculate hash with password and copy into bcc_ptr. /* Calculate hash with password and copy into bcc_ptr.
...@@ -633,7 +677,6 @@ CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses, ...@@ -633,7 +677,6 @@ CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses,
* security mode bit in Negottiate Protocol response states * security mode bit in Negottiate Protocol response states
* to use challenge/response method (i.e. Password bit is 1). * to use challenge/response method (i.e. Password bit is 1).
*/ */
rc = calc_lanman_hash(ses->password, ses->server->cryptkey, rc = calc_lanman_hash(ses->password, ses->server->cryptkey,
ses->server->sec_mode & SECMODE_PW_ENCRYPT ? ses->server->sec_mode & SECMODE_PW_ENCRYPT ?
true : false, lnm_session_key); true : false, lnm_session_key);
...@@ -641,16 +684,97 @@ CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses, ...@@ -641,16 +684,97 @@ CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses,
memcpy(bcc_ptr, (char *)lnm_session_key, CIFS_AUTH_RESP_SIZE); memcpy(bcc_ptr, (char *)lnm_session_key, CIFS_AUTH_RESP_SIZE);
bcc_ptr += CIFS_AUTH_RESP_SIZE; bcc_ptr += CIFS_AUTH_RESP_SIZE;
/* can not sign if LANMAN negotiated so no need /*
to calculate signing key? but what if server * can not sign if LANMAN negotiated so no need
changed to do higher than lanman dialect and * to calculate signing key? but what if server
we reconnected would we ever calc signing_key? */ * changed to do higher than lanman dialect and
* we reconnected would we ever calc signing_key?
*/
cifs_dbg(FYI, "Negotiating LANMAN setting up strings\n"); cifs_dbg(FYI, "Negotiating LANMAN setting up strings\n");
/* Unicode not allowed for LANMAN dialects */ /* Unicode not allowed for LANMAN dialects */
ascii_ssetup_strings(&bcc_ptr, ses, nls_cp); ascii_ssetup_strings(&bcc_ptr, ses, sess_data->nls_cp);
sess_data->iov[2].iov_len = (long) bcc_ptr -
(long) sess_data->iov[2].iov_base;
rc = sess_sendreceive(sess_data);
if (rc)
goto out;
pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
smb_buf = (struct smb_hdr *)sess_data->iov[0].iov_base;
/* lanman response has a word count of 3 */
if (smb_buf->WordCount != 3) {
rc = -EIO;
cifs_dbg(VFS, "bad word count %d\n", smb_buf->WordCount);
goto out;
}
if (le16_to_cpu(pSMB->resp.Action) & GUEST_LOGIN)
cifs_dbg(FYI, "Guest login\n"); /* BB mark SesInfo struct? */
ses->Suid = smb_buf->Uid; /* UID left in wire format (le) */
cifs_dbg(FYI, "UID = %llu\n", ses->Suid);
bytes_remaining = get_bcc(smb_buf);
bcc_ptr = pByteArea(smb_buf);
/* BB check if Unicode and decode strings */
if (bytes_remaining == 0) {
/* no string area to decode, do nothing */
} else if (smb_buf->Flags2 & SMBFLG2_UNICODE) {
/* unicode string area must be word-aligned */
if (((unsigned long) bcc_ptr - (unsigned long) smb_buf) % 2) {
++bcc_ptr;
--bytes_remaining;
}
decode_unicode_ssetup(&bcc_ptr, bytes_remaining, ses,
sess_data->nls_cp);
} else {
decode_ascii_ssetup(&bcc_ptr, bytes_remaining, ses,
sess_data->nls_cp);
}
rc = sess_establish_session(sess_data);
out:
sess_data->result = rc;
sess_data->func = NULL;
sess_free_buffer(sess_data);
}
#else
static void
sess_auth_lanman(struct sess_data *sess_data)
{
sess_data->result = -EOPNOTSUPP;
sess_data->func = NULL;
}
#endif #endif
} else if (type == NTLM) {
static void
sess_auth_ntlm(struct sess_data *sess_data)
{
int rc = 0;
struct smb_hdr *smb_buf;
SESSION_SETUP_ANDX *pSMB;
char *bcc_ptr;
struct cifs_ses *ses = sess_data->ses;
__u32 capabilities;
__u16 bytes_remaining;
/* old style NTLM sessionsetup */
/* wct = 13 */
rc = sess_alloc_buffer(sess_data, 13);
if (rc)
goto out;
pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
bcc_ptr = sess_data->iov[2].iov_base;
capabilities = cifs_ssetup_hdr(ses, pSMB);
pSMB->req_no_secext.Capabilities = cpu_to_le32(capabilities); pSMB->req_no_secext.Capabilities = cpu_to_le32(capabilities);
pSMB->req_no_secext.CaseInsensitivePasswordLength = pSMB->req_no_secext.CaseInsensitivePasswordLength =
cpu_to_le16(CIFS_AUTH_RESP_SIZE); cpu_to_le16(CIFS_AUTH_RESP_SIZE);
...@@ -658,11 +782,11 @@ CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses, ...@@ -658,11 +782,11 @@ CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses,
cpu_to_le16(CIFS_AUTH_RESP_SIZE); cpu_to_le16(CIFS_AUTH_RESP_SIZE);
/* calculate ntlm response and session key */ /* calculate ntlm response and session key */
rc = setup_ntlm_response(ses, nls_cp); rc = setup_ntlm_response(ses, sess_data->nls_cp);
if (rc) { if (rc) {
cifs_dbg(VFS, "Error %d during NTLM authentication\n", cifs_dbg(VFS, "Error %d during NTLM authentication\n",
rc); rc);
goto ssetup_exit; goto out;
} }
/* copy ntlm response */ /* copy ntlm response */
...@@ -675,26 +799,99 @@ CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses, ...@@ -675,26 +799,99 @@ CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses,
if (ses->capabilities & CAP_UNICODE) { if (ses->capabilities & CAP_UNICODE) {
/* unicode strings must be word aligned */ /* unicode strings must be word aligned */
if (iov[0].iov_len % 2) { if (sess_data->iov[0].iov_len % 2) {
*bcc_ptr = 0; *bcc_ptr = 0;
bcc_ptr++; bcc_ptr++;
} }
unicode_ssetup_strings(&bcc_ptr, ses, nls_cp); unicode_ssetup_strings(&bcc_ptr, ses, sess_data->nls_cp);
} else } else {
ascii_ssetup_strings(&bcc_ptr, ses, nls_cp); ascii_ssetup_strings(&bcc_ptr, ses, sess_data->nls_cp);
} else if (type == NTLMv2) { }
sess_data->iov[2].iov_len = (long) bcc_ptr -
(long) sess_data->iov[2].iov_base;
rc = sess_sendreceive(sess_data);
if (rc)
goto out;
pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
smb_buf = (struct smb_hdr *)sess_data->iov[0].iov_base;
if (smb_buf->WordCount != 3) {
rc = -EIO;
cifs_dbg(VFS, "bad word count %d\n", smb_buf->WordCount);
goto out;
}
if (le16_to_cpu(pSMB->resp.Action) & GUEST_LOGIN)
cifs_dbg(FYI, "Guest login\n"); /* BB mark SesInfo struct? */
ses->Suid = smb_buf->Uid; /* UID left in wire format (le) */
cifs_dbg(FYI, "UID = %llu\n", ses->Suid);
bytes_remaining = get_bcc(smb_buf);
bcc_ptr = pByteArea(smb_buf);
/* BB check if Unicode and decode strings */
if (bytes_remaining == 0) {
/* no string area to decode, do nothing */
} else if (smb_buf->Flags2 & SMBFLG2_UNICODE) {
/* unicode string area must be word-aligned */
if (((unsigned long) bcc_ptr - (unsigned long) smb_buf) % 2) {
++bcc_ptr;
--bytes_remaining;
}
decode_unicode_ssetup(&bcc_ptr, bytes_remaining, ses,
sess_data->nls_cp);
} else {
decode_ascii_ssetup(&bcc_ptr, bytes_remaining, ses,
sess_data->nls_cp);
}
rc = sess_establish_session(sess_data);
out:
sess_data->result = rc;
sess_data->func = NULL;
sess_free_buffer(sess_data);
kfree(ses->auth_key.response);
ses->auth_key.response = NULL;
}
static void
sess_auth_ntlmv2(struct sess_data *sess_data)
{
int rc = 0;
struct smb_hdr *smb_buf;
SESSION_SETUP_ANDX *pSMB;
char *bcc_ptr;
struct cifs_ses *ses = sess_data->ses;
__u32 capabilities;
__u16 bytes_remaining;
/* old style NTLM sessionsetup */
/* wct = 13 */
rc = sess_alloc_buffer(sess_data, 13);
if (rc)
goto out;
pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
bcc_ptr = sess_data->iov[2].iov_base;
capabilities = cifs_ssetup_hdr(ses, pSMB);
pSMB->req_no_secext.Capabilities = cpu_to_le32(capabilities); pSMB->req_no_secext.Capabilities = cpu_to_le32(capabilities);
/* LM2 password would be here if we supported it */ /* LM2 password would be here if we supported it */
pSMB->req_no_secext.CaseInsensitivePasswordLength = 0; pSMB->req_no_secext.CaseInsensitivePasswordLength = 0;
/* calculate nlmv2 response and session key */ /* calculate nlmv2 response and session key */
rc = setup_ntlmv2_rsp(ses, nls_cp); rc = setup_ntlmv2_rsp(ses, sess_data->nls_cp);
if (rc) { if (rc) {
cifs_dbg(VFS, "Error %d during NTLMv2 authentication\n", cifs_dbg(VFS, "Error %d during NTLMv2 authentication\n", rc);
rc); goto out;
goto ssetup_exit;
} }
memcpy(bcc_ptr, ses->auth_key.response + CIFS_SESS_KEY_SIZE, memcpy(bcc_ptr, ses->auth_key.response + CIFS_SESS_KEY_SIZE,
ses->auth_key.len - CIFS_SESS_KEY_SIZE); ses->auth_key.len - CIFS_SESS_KEY_SIZE);
bcc_ptr += ses->auth_key.len - CIFS_SESS_KEY_SIZE; bcc_ptr += ses->auth_key.len - CIFS_SESS_KEY_SIZE;
...@@ -706,196 +903,176 @@ CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses, ...@@ -706,196 +903,176 @@ CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses,
cpu_to_le16(ses->auth_key.len - CIFS_SESS_KEY_SIZE); cpu_to_le16(ses->auth_key.len - CIFS_SESS_KEY_SIZE);
if (ses->capabilities & CAP_UNICODE) { if (ses->capabilities & CAP_UNICODE) {
if (iov[0].iov_len % 2) { if (sess_data->iov[0].iov_len % 2) {
*bcc_ptr = 0; *bcc_ptr = 0;
bcc_ptr++; bcc_ptr++;
} }
unicode_ssetup_strings(&bcc_ptr, ses, nls_cp); unicode_ssetup_strings(&bcc_ptr, ses, sess_data->nls_cp);
} else } else {
ascii_ssetup_strings(&bcc_ptr, ses, nls_cp); ascii_ssetup_strings(&bcc_ptr, ses, sess_data->nls_cp);
} else if (type == Kerberos) {
#ifdef CONFIG_CIFS_UPCALL
struct cifs_spnego_msg *msg;
spnego_key = cifs_get_spnego_key(ses);
if (IS_ERR(spnego_key)) {
rc = PTR_ERR(spnego_key);
spnego_key = NULL;
goto ssetup_exit;
} }
msg = spnego_key->payload.data;
/* check version field to make sure that cifs.upcall is
sending us a response in an expected form */
if (msg->version != CIFS_SPNEGO_UPCALL_VERSION) {
cifs_dbg(VFS, "incorrect version of cifs.upcall "
"expected %d but got %d)",
CIFS_SPNEGO_UPCALL_VERSION, msg->version);
rc = -EKEYREJECTED;
goto ssetup_exit;
}
ses->auth_key.response = kmemdup(msg->data, msg->sesskey_len, sess_data->iov[2].iov_len = (long) bcc_ptr -
GFP_KERNEL); (long) sess_data->iov[2].iov_base;
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;
pSMB->req.hdr.Flags2 |= SMBFLG2_EXT_SEC; rc = sess_sendreceive(sess_data);
capabilities |= CAP_EXTENDED_SECURITY; if (rc)
pSMB->req.Capabilities = cpu_to_le32(capabilities); goto out;
iov[1].iov_base = msg->data + msg->sesskey_len;
iov[1].iov_len = msg->secblob_len;
pSMB->req.SecurityBlobLength = cpu_to_le16(iov[1].iov_len);
if (ses->capabilities & CAP_UNICODE) { pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
/* unicode strings must be word aligned */ smb_buf = (struct smb_hdr *)sess_data->iov[0].iov_base;
if ((iov[0].iov_len + iov[1].iov_len) % 2) {
*bcc_ptr = 0; if (smb_buf->WordCount != 3) {
bcc_ptr++; rc = -EIO;
} cifs_dbg(VFS, "bad word count %d\n", smb_buf->WordCount);
unicode_oslm_strings(&bcc_ptr, nls_cp); goto out;
unicode_domain_string(&bcc_ptr, ses, nls_cp);
} else
/* BB: is this right? */
ascii_ssetup_strings(&bcc_ptr, ses, nls_cp);
#else /* ! CONFIG_CIFS_UPCALL */
cifs_dbg(VFS, "Kerberos negotiated but upcall support disabled!\n");
rc = -ENOSYS;
goto ssetup_exit;
#endif /* CONFIG_CIFS_UPCALL */
} else if (type == RawNTLMSSP) {
if ((pSMB->req.hdr.Flags2 & SMBFLG2_UNICODE) == 0) {
cifs_dbg(VFS, "NTLMSSP requires Unicode support\n");
rc = -ENOSYS;
goto ssetup_exit;
} }
cifs_dbg(FYI, "ntlmssp session setup phase %d\n", phase); if (le16_to_cpu(pSMB->resp.Action) & GUEST_LOGIN)
pSMB->req.hdr.Flags2 |= SMBFLG2_EXT_SEC; cifs_dbg(FYI, "Guest login\n"); /* BB mark SesInfo struct? */
capabilities |= CAP_EXTENDED_SECURITY;
pSMB->req.Capabilities |= cpu_to_le32(capabilities); ses->Suid = smb_buf->Uid; /* UID left in wire format (le) */
switch(phase) { cifs_dbg(FYI, "UID = %llu\n", ses->Suid);
case NtLmNegotiate:
build_ntlmssp_negotiate_blob( bytes_remaining = get_bcc(smb_buf);
pSMB->req.SecurityBlob, ses); bcc_ptr = pByteArea(smb_buf);
iov[1].iov_len = sizeof(NEGOTIATE_MESSAGE);
iov[1].iov_base = pSMB->req.SecurityBlob; /* BB check if Unicode and decode strings */
pSMB->req.SecurityBlobLength = if (bytes_remaining == 0) {
cpu_to_le16(sizeof(NEGOTIATE_MESSAGE)); /* no string area to decode, do nothing */
break; } else if (smb_buf->Flags2 & SMBFLG2_UNICODE) {
case NtLmAuthenticate: /* unicode string area must be word-aligned */
/* if (((unsigned long) bcc_ptr - (unsigned long) smb_buf) % 2) {
* 5 is an empirical value, large enough to hold ++bcc_ptr;
* authenticate message plus max 10 of av paris, --bytes_remaining;
* domain, user, workstation names, flags, etc. }
*/ decode_unicode_ssetup(&bcc_ptr, bytes_remaining, ses,
ntlmsspblob = kzalloc( sess_data->nls_cp);
5*sizeof(struct _AUTHENTICATE_MESSAGE), } else {
GFP_KERNEL); decode_ascii_ssetup(&bcc_ptr, bytes_remaining, ses,
if (!ntlmsspblob) { sess_data->nls_cp);
rc = -ENOMEM;
goto ssetup_exit;
} }
rc = build_ntlmssp_auth_blob(ntlmsspblob, rc = sess_establish_session(sess_data);
&blob_len, ses, nls_cp); out:
sess_data->result = rc;
sess_data->func = NULL;
sess_free_buffer(sess_data);
kfree(ses->auth_key.response);
ses->auth_key.response = NULL;
}
#ifdef CONFIG_CIFS_UPCALL
static void
sess_auth_kerberos(struct sess_data *sess_data)
{
int rc = 0;
struct smb_hdr *smb_buf;
SESSION_SETUP_ANDX *pSMB;
char *bcc_ptr;
struct cifs_ses *ses = sess_data->ses;
__u32 capabilities;
__u16 bytes_remaining;
struct key *spnego_key = NULL;
struct cifs_spnego_msg *msg;
u16 blob_len;
/* extended security */
/* wct = 12 */
rc = sess_alloc_buffer(sess_data, 12);
if (rc) if (rc)
goto ssetup_exit; goto out;
iov[1].iov_len = blob_len;
iov[1].iov_base = ntlmsspblob; pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
pSMB->req.SecurityBlobLength = cpu_to_le16(blob_len); bcc_ptr = sess_data->iov[2].iov_base;
capabilities = cifs_ssetup_hdr(ses, pSMB);
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;
/* /*
* Make sure that we tell the server that we are using * check version field to make sure that cifs.upcall is
* the uid that it just gave us back on the response * sending us a response in an expected form
* (challenge)
*/ */
smb_buf->Uid = ses->Suid; if (msg->version != CIFS_SPNEGO_UPCALL_VERSION) {
break; cifs_dbg(VFS,
default: "incorrect version of cifs.upcall (expected %d but got %d)",
cifs_dbg(VFS, "invalid phase %d\n", phase); CIFS_SPNEGO_UPCALL_VERSION, msg->version);
rc = -ENOSYS; rc = -EKEYREJECTED;
goto ssetup_exit; 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;
pSMB->req.hdr.Flags2 |= SMBFLG2_EXT_SEC;
capabilities |= CAP_EXTENDED_SECURITY;
pSMB->req.Capabilities = cpu_to_le32(capabilities);
sess_data->iov[1].iov_base = msg->data + msg->sesskey_len;
sess_data->iov[1].iov_len = msg->secblob_len;
pSMB->req.SecurityBlobLength = cpu_to_le16(sess_data->iov[1].iov_len);
if (ses->capabilities & CAP_UNICODE) {
/* unicode strings must be word aligned */ /* unicode strings must be word aligned */
if ((iov[0].iov_len + iov[1].iov_len) % 2) { if ((sess_data->iov[0].iov_len
+ sess_data->iov[1].iov_len) % 2) {
*bcc_ptr = 0; *bcc_ptr = 0;
bcc_ptr++; bcc_ptr++;
} }
unicode_oslm_strings(&bcc_ptr, nls_cp); unicode_oslm_strings(&bcc_ptr, sess_data->nls_cp);
unicode_domain_string(&bcc_ptr, ses, sess_data->nls_cp);
} else { } else {
cifs_dbg(VFS, "secType %d not supported!\n", type); /* BB: is this right? */
rc = -ENOSYS; ascii_ssetup_strings(&bcc_ptr, ses, sess_data->nls_cp);
goto ssetup_exit;
} }
iov[2].iov_base = str_area; sess_data->iov[2].iov_len = (long) bcc_ptr -
iov[2].iov_len = (long) bcc_ptr - (long) str_area; (long) sess_data->iov[2].iov_base;
count = iov[1].iov_len + iov[2].iov_len; rc = sess_sendreceive(sess_data);
smb_buf->smb_buf_length =
cpu_to_be32(be32_to_cpu(smb_buf->smb_buf_length) + count);
put_bcc(count, smb_buf);
rc = SendReceive2(xid, ses, iov, 3 /* num_iovecs */, &resp_buf_type,
CIFS_LOG_ERROR);
/* SMB request buf freed in SendReceive2 */
pSMB = (SESSION_SETUP_ANDX *)iov[0].iov_base;
smb_buf = (struct smb_hdr *)iov[0].iov_base;
if ((type == RawNTLMSSP) && (resp_buf_type != CIFS_NO_BUFFER) &&
(smb_buf->Status.CifsError ==
cpu_to_le32(NT_STATUS_MORE_PROCESSING_REQUIRED))) {
if (phase != NtLmNegotiate) {
cifs_dbg(VFS, "Unexpected more processing error\n");
goto ssetup_exit;
}
/* NTLMSSP Negotiate sent now processing challenge (response) */
phase = NtLmChallenge; /* process ntlmssp challenge */
rc = 0; /* MORE_PROC rc is not an error here, but expected */
}
if (rc) if (rc)
goto ssetup_exit; goto out_put_spnego_key;
pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
smb_buf = (struct smb_hdr *)sess_data->iov[0].iov_base;
if ((smb_buf->WordCount != 3) && (smb_buf->WordCount != 4)) { if (smb_buf->WordCount != 4) {
rc = -EIO; rc = -EIO;
cifs_dbg(VFS, "bad word count %d\n", smb_buf->WordCount); cifs_dbg(VFS, "bad word count %d\n", smb_buf->WordCount);
goto ssetup_exit; goto out_put_spnego_key;
} }
action = le16_to_cpu(pSMB->resp.Action);
if (action & GUEST_LOGIN) if (le16_to_cpu(pSMB->resp.Action) & GUEST_LOGIN)
cifs_dbg(FYI, "Guest login\n"); /* BB mark SesInfo struct? */ cifs_dbg(FYI, "Guest login\n"); /* BB mark SesInfo struct? */
ses->Suid = smb_buf->Uid; /* UID left in wire format (le) */ ses->Suid = smb_buf->Uid; /* UID left in wire format (le) */
cifs_dbg(FYI, "UID = %llu\n", ses->Suid); cifs_dbg(FYI, "UID = %llu\n", ses->Suid);
/* response can have either 3 or 4 word count - Samba sends 3 */
/* and lanman response is 3 */
bytes_remaining = get_bcc(smb_buf); bytes_remaining = get_bcc(smb_buf);
bcc_ptr = pByteArea(smb_buf); bcc_ptr = pByteArea(smb_buf);
if (smb_buf->WordCount == 4) {
blob_len = le16_to_cpu(pSMB->resp.SecurityBlobLength); blob_len = le16_to_cpu(pSMB->resp.SecurityBlobLength);
if (blob_len > bytes_remaining) { if (blob_len > bytes_remaining) {
cifs_dbg(VFS, "bad security blob length %d\n", cifs_dbg(VFS, "bad security blob length %d\n",
blob_len); blob_len);
rc = -EINVAL; rc = -EINVAL;
goto ssetup_exit; goto out_put_spnego_key;
}
if (phase == NtLmChallenge) {
rc = decode_ntlmssp_challenge(bcc_ptr, blob_len, ses);
/* now goto beginning for ntlmssp authenticate phase */
if (rc)
goto ssetup_exit;
} }
bcc_ptr += blob_len; bcc_ptr += blob_len;
bytes_remaining -= blob_len; bytes_remaining -= blob_len;
}
/* BB check if Unicode and decode strings */ /* BB check if Unicode and decode strings */
if (bytes_remaining == 0) { if (bytes_remaining == 0) {
...@@ -906,60 +1083,371 @@ CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses, ...@@ -906,60 +1083,371 @@ CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses,
++bcc_ptr; ++bcc_ptr;
--bytes_remaining; --bytes_remaining;
} }
decode_unicode_ssetup(&bcc_ptr, bytes_remaining, ses, nls_cp); decode_unicode_ssetup(&bcc_ptr, bytes_remaining, ses,
sess_data->nls_cp);
} else { } else {
decode_ascii_ssetup(&bcc_ptr, bytes_remaining, ses, nls_cp); decode_ascii_ssetup(&bcc_ptr, bytes_remaining, ses,
sess_data->nls_cp);
} }
ssetup_exit: rc = sess_establish_session(sess_data);
if (spnego_key) { out_put_spnego_key:
key_invalidate(spnego_key); key_invalidate(spnego_key);
key_put(spnego_key); key_put(spnego_key);
out:
sess_data->result = rc;
sess_data->func = NULL;
sess_free_buffer(sess_data);
kfree(ses->auth_key.response);
ses->auth_key.response = NULL;
}
#else
static void
sess_auth_kerberos(struct sess_data *sess_data)
{
cifs_dbg(VFS, "Kerberos negotiated but upcall support disabled!\n");
sess_data->result = -ENOSYS;
sess_data->func = NULL;
}
#endif /* ! CONFIG_CIFS_UPCALL */
/*
* The required kvec buffers have to be allocated before calling this
* function.
*/
static int
_sess_auth_rawntlmssp_assemble_req(struct sess_data *sess_data)
{
struct smb_hdr *smb_buf;
SESSION_SETUP_ANDX *pSMB;
struct cifs_ses *ses = sess_data->ses;
__u32 capabilities;
char *bcc_ptr;
pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
smb_buf = (struct smb_hdr *)pSMB;
capabilities = cifs_ssetup_hdr(ses, pSMB);
if ((pSMB->req.hdr.Flags2 & SMBFLG2_UNICODE) == 0) {
cifs_dbg(VFS, "NTLMSSP requires Unicode support\n");
return -ENOSYS;
} }
kfree(str_area);
kfree(ntlmsspblob);
ntlmsspblob = NULL;
if (resp_buf_type == CIFS_SMALL_BUFFER) {
cifs_dbg(FYI, "ssetup freeing small buf %p\n", iov[0].iov_base);
cifs_small_buf_release(iov[0].iov_base);
} else if (resp_buf_type == CIFS_LARGE_BUFFER)
cifs_buf_release(iov[0].iov_base);
/* if ntlmssp, and negotiate succeeded, proceed to authenticate phase */ pSMB->req.hdr.Flags2 |= SMBFLG2_EXT_SEC;
if ((phase == NtLmChallenge) && (rc == 0)) capabilities |= CAP_EXTENDED_SECURITY;
goto ssetup_ntlmssp_authenticate; pSMB->req.Capabilities |= cpu_to_le32(capabilities);
bcc_ptr = sess_data->iov[2].iov_base;
/* unicode strings must be word aligned */
if ((sess_data->iov[0].iov_len + sess_data->iov[1].iov_len) % 2) {
*bcc_ptr = 0;
bcc_ptr++;
}
unicode_oslm_strings(&bcc_ptr, sess_data->nls_cp);
sess_data->iov[2].iov_len = (long) bcc_ptr -
(long) sess_data->iov[2].iov_base;
return 0;
}
static void
sess_auth_rawntlmssp_authenticate(struct sess_data *sess_data);
static void
sess_auth_rawntlmssp_negotiate(struct sess_data *sess_data)
{
int rc;
struct smb_hdr *smb_buf;
SESSION_SETUP_ANDX *pSMB;
struct cifs_ses *ses = sess_data->ses;
__u16 bytes_remaining;
char *bcc_ptr;
u16 blob_len;
cifs_dbg(FYI, "rawntlmssp session setup negotiate phase\n");
/*
* if memory allocation is successful, caller of this function
* frees it.
*/
ses->ntlmssp = kmalloc(sizeof(struct ntlmssp_auth), GFP_KERNEL);
if (!ses->ntlmssp) {
rc = -ENOMEM;
goto out;
}
ses->ntlmssp->sesskey_per_smbsess = false;
/* wct = 12 */
rc = sess_alloc_buffer(sess_data, 12);
if (rc)
goto out;
pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
/* Build security blob before we assemble the request */
build_ntlmssp_negotiate_blob(pSMB->req.SecurityBlob, ses);
sess_data->iov[1].iov_len = sizeof(NEGOTIATE_MESSAGE);
sess_data->iov[1].iov_base = pSMB->req.SecurityBlob;
pSMB->req.SecurityBlobLength = cpu_to_le16(sizeof(NEGOTIATE_MESSAGE));
rc = _sess_auth_rawntlmssp_assemble_req(sess_data);
if (rc)
goto out;
rc = sess_sendreceive(sess_data);
pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
smb_buf = (struct smb_hdr *)sess_data->iov[0].iov_base;
/* If true, rc here is expected and not an error */
if (sess_data->buf0_type != CIFS_NO_BUFFER &&
smb_buf->Status.CifsError ==
cpu_to_le32(NT_STATUS_MORE_PROCESSING_REQUIRED))
rc = 0;
if (rc)
goto out;
cifs_dbg(FYI, "rawntlmssp session setup challenge phase\n");
if (smb_buf->WordCount != 4) {
rc = -EIO;
cifs_dbg(VFS, "bad word count %d\n", smb_buf->WordCount);
goto out;
}
ses->Suid = smb_buf->Uid; /* UID left in wire format (le) */
cifs_dbg(FYI, "UID = %llu\n", ses->Suid);
bytes_remaining = get_bcc(smb_buf);
bcc_ptr = pByteArea(smb_buf);
blob_len = le16_to_cpu(pSMB->resp.SecurityBlobLength);
if (blob_len > bytes_remaining) {
cifs_dbg(VFS, "bad security blob length %d\n",
blob_len);
rc = -EINVAL;
goto out;
}
rc = decode_ntlmssp_challenge(bcc_ptr, blob_len, ses);
out:
sess_free_buffer(sess_data);
if (!rc) { if (!rc) {
mutex_lock(&ses->server->srv_mutex); sess_data->func = sess_auth_rawntlmssp_authenticate;
if (!ses->server->session_estab) { return;
if (ses->server->sign) { }
ses->server->session_key.response =
kmemdup(ses->auth_key.response, /* Else error. Cleanup */
ses->auth_key.len, GFP_KERNEL); kfree(ses->auth_key.response);
if (!ses->server->session_key.response) { ses->auth_key.response = NULL;
kfree(ses->ntlmssp);
ses->ntlmssp = NULL;
sess_data->func = NULL;
sess_data->result = rc;
}
static void
sess_auth_rawntlmssp_authenticate(struct sess_data *sess_data)
{
int rc;
struct smb_hdr *smb_buf;
SESSION_SETUP_ANDX *pSMB;
struct cifs_ses *ses = sess_data->ses;
__u16 bytes_remaining;
char *bcc_ptr;
char *ntlmsspblob = NULL;
u16 blob_len;
cifs_dbg(FYI, "rawntlmssp session setup authenticate phase\n");
/* wct = 12 */
rc = sess_alloc_buffer(sess_data, 12);
if (rc)
goto out;
/* Build security blob before we assemble the request */
pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
smb_buf = (struct smb_hdr *)pSMB;
/*
* 5 is an empirical value, large enough to hold
* authenticate message plus max 10 of av paris,
* domain, user, workstation names, flags, etc.
*/
ntlmsspblob = kzalloc(5*sizeof(struct _AUTHENTICATE_MESSAGE),
GFP_KERNEL);
if (!ntlmsspblob) {
rc = -ENOMEM; rc = -ENOMEM;
mutex_unlock(&ses->server->srv_mutex); goto out;
goto keycp_exit;
} }
ses->server->session_key.len =
ses->auth_key.len; rc = build_ntlmssp_auth_blob(ntlmsspblob,
&blob_len, ses, sess_data->nls_cp);
if (rc)
goto out_free_ntlmsspblob;
sess_data->iov[1].iov_len = blob_len;
sess_data->iov[1].iov_base = ntlmsspblob;
pSMB->req.SecurityBlobLength = cpu_to_le16(blob_len);
/*
* Make sure that we tell the server that we are using
* the uid that it just gave us back on the response
* (challenge)
*/
smb_buf->Uid = ses->Suid;
rc = _sess_auth_rawntlmssp_assemble_req(sess_data);
if (rc)
goto out_free_ntlmsspblob;
rc = sess_sendreceive(sess_data);
if (rc)
goto out_free_ntlmsspblob;
pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
smb_buf = (struct smb_hdr *)sess_data->iov[0].iov_base;
if (smb_buf->WordCount != 4) {
rc = -EIO;
cifs_dbg(VFS, "bad word count %d\n", smb_buf->WordCount);
goto out_free_ntlmsspblob;
} }
ses->server->sequence_number = 0x2;
ses->server->session_estab = true; if (le16_to_cpu(pSMB->resp.Action) & GUEST_LOGIN)
cifs_dbg(FYI, "Guest login\n"); /* BB mark SesInfo struct? */
bytes_remaining = get_bcc(smb_buf);
bcc_ptr = pByteArea(smb_buf);
blob_len = le16_to_cpu(pSMB->resp.SecurityBlobLength);
if (blob_len > bytes_remaining) {
cifs_dbg(VFS, "bad security blob length %d\n",
blob_len);
rc = -EINVAL;
goto out_free_ntlmsspblob;
} }
mutex_unlock(&ses->server->srv_mutex); bcc_ptr += blob_len;
bytes_remaining -= blob_len;
cifs_dbg(FYI, "CIFS session established successfully\n");
spin_lock(&GlobalMid_Lock); /* BB check if Unicode and decode strings */
ses->status = CifsGood; if (bytes_remaining == 0) {
ses->need_reconnect = false; /* no string area to decode, do nothing */
spin_unlock(&GlobalMid_Lock); } else if (smb_buf->Flags2 & SMBFLG2_UNICODE) {
/* unicode string area must be word-aligned */
if (((unsigned long) bcc_ptr - (unsigned long) smb_buf) % 2) {
++bcc_ptr;
--bytes_remaining;
} }
decode_unicode_ssetup(&bcc_ptr, bytes_remaining, ses,
sess_data->nls_cp);
} else {
decode_ascii_ssetup(&bcc_ptr, bytes_remaining, ses,
sess_data->nls_cp);
}
out_free_ntlmsspblob:
kfree(ntlmsspblob);
out:
sess_free_buffer(sess_data);
if (!rc)
rc = sess_establish_session(sess_data);
keycp_exit: /* Cleanup */
kfree(ses->auth_key.response); kfree(ses->auth_key.response);
ses->auth_key.response = NULL; ses->auth_key.response = NULL;
kfree(ses->ntlmssp); kfree(ses->ntlmssp);
ses->ntlmssp = NULL;
sess_data->func = NULL;
sess_data->result = rc;
}
static int select_sec(struct cifs_ses *ses, struct sess_data *sess_data)
{
int type;
type = select_sectype(ses->server, ses->sectype);
cifs_dbg(FYI, "sess setup type %d\n", type);
if (type == Unspecified) {
cifs_dbg(VFS,
"Unable to select appropriate authentication method!");
return -EINVAL;
}
switch (type) {
case LANMAN:
/* LANMAN and plaintext are less secure and off by default.
* So we make this explicitly be turned on in kconfig (in the
* build) and turned on at runtime (changed from the default)
* in proc/fs/cifs or via mount parm. Unfortunately this is
* needed for old Win (e.g. Win95), some obscure NAS and OS/2 */
#ifdef CONFIG_CIFS_WEAK_PW_HASH
sess_data->func = sess_auth_lanman;
break;
#else
return -EOPNOTSUPP;
#endif
case NTLM:
sess_data->func = sess_auth_ntlm;
break;
case NTLMv2:
sess_data->func = sess_auth_ntlmv2;
break;
case Kerberos:
#ifdef CONFIG_CIFS_UPCALL
sess_data->func = sess_auth_kerberos;
break;
#else
cifs_dbg(VFS, "Kerberos negotiated but upcall support disabled!\n");
return -ENOSYS;
break;
#endif /* CONFIG_CIFS_UPCALL */
case RawNTLMSSP:
sess_data->func = sess_auth_rawntlmssp_negotiate;
break;
default:
cifs_dbg(VFS, "secType %d not supported!\n", type);
return -ENOSYS;
}
return 0;
}
int CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses,
const struct nls_table *nls_cp)
{
int rc = 0;
struct sess_data *sess_data;
if (ses == NULL) {
WARN(1, "%s: ses == NULL!", __func__);
return -EINVAL;
}
sess_data = kzalloc(sizeof(struct sess_data), GFP_KERNEL);
if (!sess_data)
return -ENOMEM;
rc = 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);
/* Store result before we free sess_data */
rc = sess_data->result;
out:
kfree(sess_data);
return rc; return rc;
} }
...@@ -1009,6 +1009,12 @@ cifs_is_read_op(__u32 oplock) ...@@ -1009,6 +1009,12 @@ cifs_is_read_op(__u32 oplock)
return oplock == OPLOCK_READ; return oplock == OPLOCK_READ;
} }
static unsigned int
cifs_wp_retry_size(struct inode *inode)
{
return CIFS_SB(inode->i_sb)->wsize;
}
struct smb_version_operations smb1_operations = { struct smb_version_operations smb1_operations = {
.send_cancel = send_nt_cancel, .send_cancel = send_nt_cancel,
.compare_fids = cifs_compare_fids, .compare_fids = cifs_compare_fids,
...@@ -1019,6 +1025,7 @@ struct smb_version_operations smb1_operations = { ...@@ -1019,6 +1025,7 @@ struct smb_version_operations smb1_operations = {
.set_credits = cifs_set_credits, .set_credits = cifs_set_credits,
.get_credits_field = cifs_get_credits_field, .get_credits_field = cifs_get_credits_field,
.get_credits = cifs_get_credits, .get_credits = cifs_get_credits,
.wait_mtu_credits = cifs_wait_mtu_credits,
.get_next_mid = cifs_get_next_mid, .get_next_mid = cifs_get_next_mid,
.read_data_offset = cifs_read_data_offset, .read_data_offset = cifs_read_data_offset,
.read_data_length = cifs_read_data_length, .read_data_length = cifs_read_data_length,
...@@ -1078,6 +1085,7 @@ struct smb_version_operations smb1_operations = { ...@@ -1078,6 +1085,7 @@ struct smb_version_operations smb1_operations = {
.query_mf_symlink = cifs_query_mf_symlink, .query_mf_symlink = cifs_query_mf_symlink,
.create_mf_symlink = cifs_create_mf_symlink, .create_mf_symlink = cifs_create_mf_symlink,
.is_read_op = cifs_is_read_op, .is_read_op = cifs_is_read_op,
.wp_retry_size = cifs_wp_retry_size,
#ifdef CONFIG_CIFS_XATTR #ifdef CONFIG_CIFS_XATTR
.query_all_EAs = CIFSSMBQAllEAs, .query_all_EAs = CIFSSMBQAllEAs,
.set_EA = CIFSSMBSetEA, .set_EA = CIFSSMBSetEA,
......
...@@ -91,7 +91,7 @@ smb2_open_op_close(const unsigned int xid, struct cifs_tcon *tcon, ...@@ -91,7 +91,7 @@ smb2_open_op_close(const unsigned int xid, struct cifs_tcon *tcon,
case SMB2_OP_SET_EOF: case SMB2_OP_SET_EOF:
tmprc = SMB2_set_eof(xid, tcon, fid.persistent_fid, tmprc = SMB2_set_eof(xid, tcon, fid.persistent_fid,
fid.volatile_fid, current->tgid, fid.volatile_fid, current->tgid,
(__le64 *)data); (__le64 *)data, false);
break; break;
case SMB2_OP_SET_INFO: case SMB2_OP_SET_INFO:
tmprc = SMB2_set_info(xid, tcon, fid.persistent_fid, tmprc = SMB2_set_info(xid, tcon, fid.persistent_fid,
......
...@@ -605,7 +605,7 @@ static const struct status_to_posix_error smb2_error_map_table[] = { ...@@ -605,7 +605,7 @@ static const struct status_to_posix_error smb2_error_map_table[] = {
{STATUS_MAPPED_FILE_SIZE_ZERO, -EIO, "STATUS_MAPPED_FILE_SIZE_ZERO"}, {STATUS_MAPPED_FILE_SIZE_ZERO, -EIO, "STATUS_MAPPED_FILE_SIZE_ZERO"},
{STATUS_TOO_MANY_OPENED_FILES, -EMFILE, "STATUS_TOO_MANY_OPENED_FILES"}, {STATUS_TOO_MANY_OPENED_FILES, -EMFILE, "STATUS_TOO_MANY_OPENED_FILES"},
{STATUS_CANCELLED, -EIO, "STATUS_CANCELLED"}, {STATUS_CANCELLED, -EIO, "STATUS_CANCELLED"},
{STATUS_CANNOT_DELETE, -EIO, "STATUS_CANNOT_DELETE"}, {STATUS_CANNOT_DELETE, -EACCES, "STATUS_CANNOT_DELETE"},
{STATUS_INVALID_COMPUTER_NAME, -EIO, "STATUS_INVALID_COMPUTER_NAME"}, {STATUS_INVALID_COMPUTER_NAME, -EIO, "STATUS_INVALID_COMPUTER_NAME"},
{STATUS_FILE_DELETED, -EIO, "STATUS_FILE_DELETED"}, {STATUS_FILE_DELETED, -EIO, "STATUS_FILE_DELETED"},
{STATUS_SPECIAL_ACCOUNT, -EIO, "STATUS_SPECIAL_ACCOUNT"}, {STATUS_SPECIAL_ACCOUNT, -EIO, "STATUS_SPECIAL_ACCOUNT"},
......
...@@ -437,7 +437,7 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp, ...@@ -437,7 +437,7 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
continue; continue;
cifs_dbg(FYI, "found in the open list\n"); cifs_dbg(FYI, "found in the open list\n");
cifs_dbg(FYI, "lease key match, lease break 0x%d\n", cifs_dbg(FYI, "lease key match, lease break 0x%x\n",
le32_to_cpu(rsp->NewLeaseState)); le32_to_cpu(rsp->NewLeaseState));
server->ops->set_oplock_level(cinode, lease_state, 0, NULL); server->ops->set_oplock_level(cinode, lease_state, 0, NULL);
...@@ -467,7 +467,7 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp, ...@@ -467,7 +467,7 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
} }
cifs_dbg(FYI, "found in the pending open list\n"); cifs_dbg(FYI, "found in the pending open list\n");
cifs_dbg(FYI, "lease key match, lease break 0x%d\n", cifs_dbg(FYI, "lease key match, lease break 0x%x\n",
le32_to_cpu(rsp->NewLeaseState)); le32_to_cpu(rsp->NewLeaseState));
open->oplock = lease_state; open->oplock = lease_state;
...@@ -546,7 +546,7 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server) ...@@ -546,7 +546,7 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server)
return false; return false;
} }
cifs_dbg(FYI, "oplock level 0x%d\n", rsp->OplockLevel); cifs_dbg(FYI, "oplock level 0x%x\n", rsp->OplockLevel);
/* look up tcon based on tid & uid */ /* look up tcon based on tid & uid */
spin_lock(&cifs_tcp_ses_lock); spin_lock(&cifs_tcp_ses_lock);
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include <linux/pagemap.h> #include <linux/pagemap.h>
#include <linux/vfs.h> #include <linux/vfs.h>
#include <linux/falloc.h>
#include "cifsglob.h" #include "cifsglob.h"
#include "smb2pdu.h" #include "smb2pdu.h"
#include "smb2proto.h" #include "smb2proto.h"
...@@ -112,6 +113,53 @@ smb2_get_credits(struct mid_q_entry *mid) ...@@ -112,6 +113,53 @@ smb2_get_credits(struct mid_q_entry *mid)
return le16_to_cpu(((struct smb2_hdr *)mid->resp_buf)->CreditRequest); return le16_to_cpu(((struct smb2_hdr *)mid->resp_buf)->CreditRequest);
} }
static int
smb2_wait_mtu_credits(struct TCP_Server_Info *server, unsigned int size,
unsigned int *num, unsigned int *credits)
{
int rc = 0;
unsigned int scredits;
spin_lock(&server->req_lock);
while (1) {
if (server->credits <= 0) {
spin_unlock(&server->req_lock);
cifs_num_waiters_inc(server);
rc = wait_event_killable(server->request_q,
has_credits(server, &server->credits));
cifs_num_waiters_dec(server);
if (rc)
return rc;
spin_lock(&server->req_lock);
} else {
if (server->tcpStatus == CifsExiting) {
spin_unlock(&server->req_lock);
return -ENOENT;
}
scredits = server->credits;
/* can deadlock with reopen */
if (scredits == 1) {
*num = SMB2_MAX_BUFFER_SIZE;
*credits = 0;
break;
}
/* leave one credit for a possible reopen */
scredits--;
*num = min_t(unsigned int, size,
scredits * SMB2_MAX_BUFFER_SIZE);
*credits = DIV_ROUND_UP(*num, SMB2_MAX_BUFFER_SIZE);
server->credits -= *credits;
server->in_flight++;
break;
}
}
spin_unlock(&server->req_lock);
return rc;
}
static __u64 static __u64
smb2_get_next_mid(struct TCP_Server_Info *server) smb2_get_next_mid(struct TCP_Server_Info *server)
{ {
...@@ -182,7 +230,8 @@ smb2_negotiate_wsize(struct cifs_tcon *tcon, struct smb_vol *volume_info) ...@@ -182,7 +230,8 @@ smb2_negotiate_wsize(struct cifs_tcon *tcon, struct smb_vol *volume_info)
/* start with specified wsize, or default */ /* start with specified wsize, or default */
wsize = volume_info->wsize ? volume_info->wsize : CIFS_DEFAULT_IOSIZE; wsize = volume_info->wsize ? volume_info->wsize : CIFS_DEFAULT_IOSIZE;
wsize = min_t(unsigned int, wsize, server->max_write); wsize = min_t(unsigned int, wsize, server->max_write);
/* set it to the maximum buffer size value we can send with 1 credit */
if (!(server->capabilities & SMB2_GLOBAL_CAP_LARGE_MTU))
wsize = min_t(unsigned int, wsize, SMB2_MAX_BUFFER_SIZE); wsize = min_t(unsigned int, wsize, SMB2_MAX_BUFFER_SIZE);
return wsize; return wsize;
...@@ -197,7 +246,8 @@ smb2_negotiate_rsize(struct cifs_tcon *tcon, struct smb_vol *volume_info) ...@@ -197,7 +246,8 @@ smb2_negotiate_rsize(struct cifs_tcon *tcon, struct smb_vol *volume_info)
/* start with specified rsize, or default */ /* start with specified rsize, or default */
rsize = volume_info->rsize ? volume_info->rsize : CIFS_DEFAULT_IOSIZE; rsize = volume_info->rsize ? volume_info->rsize : CIFS_DEFAULT_IOSIZE;
rsize = min_t(unsigned int, rsize, server->max_read); rsize = min_t(unsigned int, rsize, server->max_read);
/* set it to the maximum buffer size value we can send with 1 credit */
if (!(server->capabilities & SMB2_GLOBAL_CAP_LARGE_MTU))
rsize = min_t(unsigned int, rsize, SMB2_MAX_BUFFER_SIZE); rsize = min_t(unsigned int, rsize, SMB2_MAX_BUFFER_SIZE);
return rsize; return rsize;
...@@ -687,7 +737,7 @@ smb2_set_file_size(const unsigned int xid, struct cifs_tcon *tcon, ...@@ -687,7 +737,7 @@ smb2_set_file_size(const unsigned int xid, struct cifs_tcon *tcon,
{ {
__le64 eof = cpu_to_le64(size); __le64 eof = cpu_to_le64(size);
return SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, return SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid,
cfile->fid.volatile_fid, cfile->pid, &eof); cfile->fid.volatile_fid, cfile->pid, &eof, false);
} }
static int static int
...@@ -1104,6 +1154,13 @@ smb3_parse_lease_buf(void *buf, unsigned int *epoch) ...@@ -1104,6 +1154,13 @@ smb3_parse_lease_buf(void *buf, unsigned int *epoch)
return le32_to_cpu(lc->lcontext.LeaseState); return le32_to_cpu(lc->lcontext.LeaseState);
} }
static unsigned int
smb2_wp_retry_size(struct inode *inode)
{
return min_t(unsigned int, CIFS_SB(inode->i_sb)->wsize,
SMB2_MAX_BUFFER_SIZE);
}
struct smb_version_operations smb20_operations = { struct smb_version_operations smb20_operations = {
.compare_fids = smb2_compare_fids, .compare_fids = smb2_compare_fids,
.setup_request = smb2_setup_request, .setup_request = smb2_setup_request,
...@@ -1113,6 +1170,7 @@ struct smb_version_operations smb20_operations = { ...@@ -1113,6 +1170,7 @@ struct smb_version_operations smb20_operations = {
.set_credits = smb2_set_credits, .set_credits = smb2_set_credits,
.get_credits_field = smb2_get_credits_field, .get_credits_field = smb2_get_credits_field,
.get_credits = smb2_get_credits, .get_credits = smb2_get_credits,
.wait_mtu_credits = cifs_wait_mtu_credits,
.get_next_mid = smb2_get_next_mid, .get_next_mid = smb2_get_next_mid,
.read_data_offset = smb2_read_data_offset, .read_data_offset = smb2_read_data_offset,
.read_data_length = smb2_read_data_length, .read_data_length = smb2_read_data_length,
...@@ -1177,6 +1235,7 @@ struct smb_version_operations smb20_operations = { ...@@ -1177,6 +1235,7 @@ struct smb_version_operations smb20_operations = {
.create_lease_buf = smb2_create_lease_buf, .create_lease_buf = smb2_create_lease_buf,
.parse_lease_buf = smb2_parse_lease_buf, .parse_lease_buf = smb2_parse_lease_buf,
.clone_range = smb2_clone_range, .clone_range = smb2_clone_range,
.wp_retry_size = smb2_wp_retry_size,
}; };
struct smb_version_operations smb21_operations = { struct smb_version_operations smb21_operations = {
...@@ -1188,6 +1247,7 @@ struct smb_version_operations smb21_operations = { ...@@ -1188,6 +1247,7 @@ struct smb_version_operations smb21_operations = {
.set_credits = smb2_set_credits, .set_credits = smb2_set_credits,
.get_credits_field = smb2_get_credits_field, .get_credits_field = smb2_get_credits_field,
.get_credits = smb2_get_credits, .get_credits = smb2_get_credits,
.wait_mtu_credits = smb2_wait_mtu_credits,
.get_next_mid = smb2_get_next_mid, .get_next_mid = smb2_get_next_mid,
.read_data_offset = smb2_read_data_offset, .read_data_offset = smb2_read_data_offset,
.read_data_length = smb2_read_data_length, .read_data_length = smb2_read_data_length,
...@@ -1252,6 +1312,7 @@ struct smb_version_operations smb21_operations = { ...@@ -1252,6 +1312,7 @@ struct smb_version_operations smb21_operations = {
.create_lease_buf = smb2_create_lease_buf, .create_lease_buf = smb2_create_lease_buf,
.parse_lease_buf = smb2_parse_lease_buf, .parse_lease_buf = smb2_parse_lease_buf,
.clone_range = smb2_clone_range, .clone_range = smb2_clone_range,
.wp_retry_size = smb2_wp_retry_size,
}; };
struct smb_version_operations smb30_operations = { struct smb_version_operations smb30_operations = {
...@@ -1263,6 +1324,7 @@ struct smb_version_operations smb30_operations = { ...@@ -1263,6 +1324,7 @@ struct smb_version_operations smb30_operations = {
.set_credits = smb2_set_credits, .set_credits = smb2_set_credits,
.get_credits_field = smb2_get_credits_field, .get_credits_field = smb2_get_credits_field,
.get_credits = smb2_get_credits, .get_credits = smb2_get_credits,
.wait_mtu_credits = smb2_wait_mtu_credits,
.get_next_mid = smb2_get_next_mid, .get_next_mid = smb2_get_next_mid,
.read_data_offset = smb2_read_data_offset, .read_data_offset = smb2_read_data_offset,
.read_data_length = smb2_read_data_length, .read_data_length = smb2_read_data_length,
...@@ -1330,6 +1392,7 @@ struct smb_version_operations smb30_operations = { ...@@ -1330,6 +1392,7 @@ struct smb_version_operations smb30_operations = {
.parse_lease_buf = smb3_parse_lease_buf, .parse_lease_buf = smb3_parse_lease_buf,
.clone_range = smb2_clone_range, .clone_range = smb2_clone_range,
.validate_negotiate = smb3_validate_negotiate, .validate_negotiate = smb3_validate_negotiate,
.wp_retry_size = smb2_wp_retry_size,
}; };
struct smb_version_values smb20_values = { struct smb_version_values smb20_values = {
......
...@@ -108,7 +108,6 @@ smb2_hdr_assemble(struct smb2_hdr *hdr, __le16 smb2_cmd /* command */ , ...@@ -108,7 +108,6 @@ smb2_hdr_assemble(struct smb2_hdr *hdr, __le16 smb2_cmd /* command */ ,
if (!tcon) if (!tcon)
goto out; goto out;
/* BB FIXME when we do write > 64K add +1 for every 64K in req or rsp */
/* GLOBAL_CAP_LARGE_MTU will only be set if dialect > SMB2.02 */ /* GLOBAL_CAP_LARGE_MTU will only be set if dialect > SMB2.02 */
/* See sections 2.2.4 and 3.2.4.1.5 of MS-SMB2 */ /* See sections 2.2.4 and 3.2.4.1.5 of MS-SMB2 */
if ((tcon->ses) && if ((tcon->ses) &&
...@@ -245,10 +244,6 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon) ...@@ -245,10 +244,6 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon)
if (rc) if (rc)
goto out; goto out;
atomic_inc(&tconInfoReconnectCount); atomic_inc(&tconInfoReconnectCount);
/*
* BB FIXME add code to check if wsize needs update due to negotiated
* smb buffer size shrinking.
*/
out: out:
/* /*
* Check if handle based operation so we know whether we can continue * Check if handle based operation so we know whether we can continue
...@@ -309,16 +304,6 @@ small_smb2_init(__le16 smb2_command, struct cifs_tcon *tcon, ...@@ -309,16 +304,6 @@ small_smb2_init(__le16 smb2_command, struct cifs_tcon *tcon,
return rc; return rc;
} }
static void
free_rsp_buf(int resp_buftype, void *rsp)
{
if (resp_buftype == CIFS_SMALL_BUFFER)
cifs_small_buf_release(rsp);
else if (resp_buftype == CIFS_LARGE_BUFFER)
cifs_buf_release(rsp);
}
/* /*
* *
* SMB2 Worker functions follow: * SMB2 Worker functions follow:
...@@ -1738,12 +1723,18 @@ smb2_readv_callback(struct mid_q_entry *mid) ...@@ -1738,12 +1723,18 @@ smb2_readv_callback(struct mid_q_entry *mid)
rc); rc);
} }
/* FIXME: should this be counted toward the initiating task? */ /* FIXME: should this be counted toward the initiating task? */
task_io_account_read(rdata->bytes); task_io_account_read(rdata->got_bytes);
cifs_stats_bytes_read(tcon, rdata->bytes); cifs_stats_bytes_read(tcon, rdata->got_bytes);
break; break;
case MID_REQUEST_SUBMITTED: case MID_REQUEST_SUBMITTED:
case MID_RETRY_NEEDED: case MID_RETRY_NEEDED:
rdata->result = -EAGAIN; rdata->result = -EAGAIN;
if (server->sign && rdata->got_bytes)
/* reset bytes number since we can not check a sign */
rdata->got_bytes = 0;
/* FIXME: should this be counted toward the initiating task? */
task_io_account_read(rdata->got_bytes);
cifs_stats_bytes_read(tcon, rdata->got_bytes);
break; break;
default: default:
if (rdata->result != -ENODATA) if (rdata->result != -ENODATA)
...@@ -1762,11 +1753,12 @@ smb2_readv_callback(struct mid_q_entry *mid) ...@@ -1762,11 +1753,12 @@ smb2_readv_callback(struct mid_q_entry *mid)
int int
smb2_async_readv(struct cifs_readdata *rdata) smb2_async_readv(struct cifs_readdata *rdata)
{ {
int rc; int rc, flags = 0;
struct smb2_hdr *buf; struct smb2_hdr *buf;
struct cifs_io_parms io_parms; struct cifs_io_parms io_parms;
struct smb_rqst rqst = { .rq_iov = &rdata->iov, struct smb_rqst rqst = { .rq_iov = &rdata->iov,
.rq_nvec = 1 }; .rq_nvec = 1 };
struct TCP_Server_Info *server;
cifs_dbg(FYI, "%s: offset=%llu bytes=%u\n", cifs_dbg(FYI, "%s: offset=%llu bytes=%u\n",
__func__, rdata->offset, rdata->bytes); __func__, rdata->offset, rdata->bytes);
...@@ -1777,18 +1769,41 @@ smb2_async_readv(struct cifs_readdata *rdata) ...@@ -1777,18 +1769,41 @@ smb2_async_readv(struct cifs_readdata *rdata)
io_parms.persistent_fid = rdata->cfile->fid.persistent_fid; io_parms.persistent_fid = rdata->cfile->fid.persistent_fid;
io_parms.volatile_fid = rdata->cfile->fid.volatile_fid; io_parms.volatile_fid = rdata->cfile->fid.volatile_fid;
io_parms.pid = rdata->pid; io_parms.pid = rdata->pid;
server = io_parms.tcon->ses->server;
rc = smb2_new_read_req(&rdata->iov, &io_parms, 0, 0); rc = smb2_new_read_req(&rdata->iov, &io_parms, 0, 0);
if (rc) if (rc) {
if (rc == -EAGAIN && rdata->credits) {
/* credits was reset by reconnect */
rdata->credits = 0;
/* reduce in_flight value since we won't send the req */
spin_lock(&server->req_lock);
server->in_flight--;
spin_unlock(&server->req_lock);
}
return rc; return rc;
}
buf = (struct smb2_hdr *)rdata->iov.iov_base; buf = (struct smb2_hdr *)rdata->iov.iov_base;
/* 4 for rfc1002 length field */ /* 4 for rfc1002 length field */
rdata->iov.iov_len = get_rfc1002_length(rdata->iov.iov_base) + 4; rdata->iov.iov_len = get_rfc1002_length(rdata->iov.iov_base) + 4;
if (rdata->credits) {
buf->CreditCharge = cpu_to_le16(DIV_ROUND_UP(rdata->bytes,
SMB2_MAX_BUFFER_SIZE));
spin_lock(&server->req_lock);
server->credits += rdata->credits -
le16_to_cpu(buf->CreditCharge);
spin_unlock(&server->req_lock);
wake_up(&server->request_q);
flags = CIFS_HAS_CREDITS;
}
kref_get(&rdata->refcount); kref_get(&rdata->refcount);
rc = cifs_call_async(io_parms.tcon->ses->server, &rqst, rc = cifs_call_async(io_parms.tcon->ses->server, &rqst,
cifs_readv_receive, smb2_readv_callback, cifs_readv_receive, smb2_readv_callback,
rdata, 0); rdata, flags);
if (rc) { if (rc) {
kref_put(&rdata->refcount, cifs_readdata_release); kref_put(&rdata->refcount, cifs_readdata_release);
cifs_stats_fail_inc(io_parms.tcon, SMB2_READ_HE); cifs_stats_fail_inc(io_parms.tcon, SMB2_READ_HE);
...@@ -1906,15 +1921,25 @@ int ...@@ -1906,15 +1921,25 @@ int
smb2_async_writev(struct cifs_writedata *wdata, smb2_async_writev(struct cifs_writedata *wdata,
void (*release)(struct kref *kref)) void (*release)(struct kref *kref))
{ {
int rc = -EACCES; int rc = -EACCES, flags = 0;
struct smb2_write_req *req = NULL; struct smb2_write_req *req = NULL;
struct cifs_tcon *tcon = tlink_tcon(wdata->cfile->tlink); struct cifs_tcon *tcon = tlink_tcon(wdata->cfile->tlink);
struct TCP_Server_Info *server = tcon->ses->server;
struct kvec iov; struct kvec iov;
struct smb_rqst rqst; struct smb_rqst rqst;
rc = small_smb2_init(SMB2_WRITE, tcon, (void **) &req); rc = small_smb2_init(SMB2_WRITE, tcon, (void **) &req);
if (rc) if (rc) {
if (rc == -EAGAIN && wdata->credits) {
/* credits was reset by reconnect */
wdata->credits = 0;
/* reduce in_flight value since we won't send the req */
spin_lock(&server->req_lock);
server->in_flight--;
spin_unlock(&server->req_lock);
}
goto async_writev_out; goto async_writev_out;
}
req->hdr.ProcessId = cpu_to_le32(wdata->cfile->pid); req->hdr.ProcessId = cpu_to_le32(wdata->cfile->pid);
...@@ -1947,9 +1972,20 @@ smb2_async_writev(struct cifs_writedata *wdata, ...@@ -1947,9 +1972,20 @@ smb2_async_writev(struct cifs_writedata *wdata,
inc_rfc1001_len(&req->hdr, wdata->bytes - 1 /* Buffer */); inc_rfc1001_len(&req->hdr, wdata->bytes - 1 /* Buffer */);
if (wdata->credits) {
req->hdr.CreditCharge = cpu_to_le16(DIV_ROUND_UP(wdata->bytes,
SMB2_MAX_BUFFER_SIZE));
spin_lock(&server->req_lock);
server->credits += wdata->credits -
le16_to_cpu(req->hdr.CreditCharge);
spin_unlock(&server->req_lock);
wake_up(&server->request_q);
flags = CIFS_HAS_CREDITS;
}
kref_get(&wdata->refcount); kref_get(&wdata->refcount);
rc = cifs_call_async(tcon->ses->server, &rqst, NULL, rc = cifs_call_async(server, &rqst, NULL, smb2_writev_callback, wdata,
smb2_writev_callback, wdata, 0); flags);
if (rc) { if (rc) {
kref_put(&wdata->refcount, release); kref_put(&wdata->refcount, release);
...@@ -2325,7 +2361,7 @@ SMB2_set_hardlink(const unsigned int xid, struct cifs_tcon *tcon, ...@@ -2325,7 +2361,7 @@ SMB2_set_hardlink(const unsigned int xid, struct cifs_tcon *tcon,
int int
SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid,
u64 volatile_fid, u32 pid, __le64 *eof) u64 volatile_fid, u32 pid, __le64 *eof, bool is_falloc)
{ {
struct smb2_file_eof_info info; struct smb2_file_eof_info info;
void *data; void *data;
...@@ -2336,8 +2372,12 @@ SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, ...@@ -2336,8 +2372,12 @@ SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid,
data = &info; data = &info;
size = sizeof(struct smb2_file_eof_info); size = sizeof(struct smb2_file_eof_info);
return send_set_info(xid, tcon, persistent_fid, volatile_fid, pid, if (is_falloc)
FILE_END_OF_FILE_INFORMATION, 1, &data, &size); return send_set_info(xid, tcon, persistent_fid, volatile_fid,
pid, FILE_ALLOCATION_INFORMATION, 1, &data, &size);
else
return send_set_info(xid, tcon, persistent_fid, volatile_fid,
pid, FILE_END_OF_FILE_INFORMATION, 1, &data, &size);
} }
int int
......
...@@ -139,7 +139,7 @@ extern int SMB2_set_hardlink(const unsigned int xid, struct cifs_tcon *tcon, ...@@ -139,7 +139,7 @@ extern int SMB2_set_hardlink(const unsigned int xid, struct cifs_tcon *tcon,
__le16 *target_file); __le16 *target_file);
extern int SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon, extern int SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon,
u64 persistent_fid, u64 volatile_fid, u32 pid, u64 persistent_fid, u64 volatile_fid, u32 pid,
__le64 *eof); __le64 *eof, bool is_fallocate);
extern int SMB2_set_info(const unsigned int xid, struct cifs_tcon *tcon, extern int SMB2_set_info(const unsigned int xid, struct cifs_tcon *tcon,
u64 persistent_fid, u64 volatile_fid, u64 persistent_fid, u64 volatile_fid,
FILE_BASIC_INFO *buf); FILE_BASIC_INFO *buf);
......
...@@ -466,7 +466,12 @@ smb2_verify_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) ...@@ -466,7 +466,12 @@ smb2_verify_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
static inline void static inline void
smb2_seq_num_into_buf(struct TCP_Server_Info *server, struct smb2_hdr *hdr) smb2_seq_num_into_buf(struct TCP_Server_Info *server, struct smb2_hdr *hdr)
{ {
unsigned int i, num = le16_to_cpu(hdr->CreditCharge);
hdr->MessageId = get_next_mid64(server); hdr->MessageId = get_next_mid64(server);
/* skip message numbers according to CreditCharge field */
for (i = 1; i < num; i++)
get_next_mid(server);
} }
static struct mid_q_entry * static struct mid_q_entry *
......
...@@ -448,6 +448,15 @@ wait_for_free_request(struct TCP_Server_Info *server, const int timeout, ...@@ -448,6 +448,15 @@ wait_for_free_request(struct TCP_Server_Info *server, const int timeout,
return wait_for_free_credits(server, timeout, val); return wait_for_free_credits(server, timeout, val);
} }
int
cifs_wait_mtu_credits(struct TCP_Server_Info *server, unsigned int size,
unsigned int *num, unsigned int *credits)
{
*num = size;
*credits = 0;
return 0;
}
static int allocate_mid(struct cifs_ses *ses, struct smb_hdr *in_buf, static int allocate_mid(struct cifs_ses *ses, struct smb_hdr *in_buf,
struct mid_q_entry **ppmidQ) struct mid_q_entry **ppmidQ)
{ {
...@@ -531,20 +540,23 @@ cifs_call_async(struct TCP_Server_Info *server, struct smb_rqst *rqst, ...@@ -531,20 +540,23 @@ cifs_call_async(struct TCP_Server_Info *server, struct smb_rqst *rqst,
{ {
int rc, timeout, optype; int rc, timeout, optype;
struct mid_q_entry *mid; struct mid_q_entry *mid;
unsigned int credits = 0;
timeout = flags & CIFS_TIMEOUT_MASK; timeout = flags & CIFS_TIMEOUT_MASK;
optype = flags & CIFS_OP_MASK; optype = flags & CIFS_OP_MASK;
if ((flags & CIFS_HAS_CREDITS) == 0) {
rc = wait_for_free_request(server, timeout, optype); rc = wait_for_free_request(server, timeout, optype);
if (rc) if (rc)
return rc; return rc;
credits = 1;
}
mutex_lock(&server->srv_mutex); mutex_lock(&server->srv_mutex);
mid = server->ops->setup_async_request(server, rqst); mid = server->ops->setup_async_request(server, rqst);
if (IS_ERR(mid)) { if (IS_ERR(mid)) {
mutex_unlock(&server->srv_mutex); mutex_unlock(&server->srv_mutex);
add_credits(server, 1, optype); add_credits_and_wake_if(server, credits, optype);
wake_up(&server->request_q);
return PTR_ERR(mid); return PTR_ERR(mid);
} }
...@@ -572,8 +584,7 @@ cifs_call_async(struct TCP_Server_Info *server, struct smb_rqst *rqst, ...@@ -572,8 +584,7 @@ cifs_call_async(struct TCP_Server_Info *server, struct smb_rqst *rqst,
return 0; return 0;
cifs_delete_mid(mid); cifs_delete_mid(mid);
add_credits(server, 1, optype); add_credits_and_wake_if(server, credits, optype);
wake_up(&server->request_q);
return rc; return rc;
} }
......
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