Pull power management and ACPI updates from Rafael Wysocki:
"This time the total number of ACPI commits is slightly greater than
the number of cpufreq commits, but Viresh Kumar (who works on cpufreq)
remains the most active patch submitter.
To me, the most significant change is the addition of offline/online
device operations to the driver core (with the Greg's blessing) and
the related modifications of the ACPI core hotplug code. Next are the
freezer updates from Colin Cross that should make the freezing of
tasks a bit less heavy weight.
We also have a couple of regression fixes, a number of fixes for
issues that have not been identified as regressions, two new drivers
and a bunch of cleanups all over.
Highlights:
- Hotplug changes to support graceful hot-removal failures.
It sometimes is necessary to fail device hot-removal operations
gracefully if they cannot be carried out completely. For example,
if memory from a memory module being hot-removed has been allocated
for the kernel's own use and cannot be moved elsewhere, it's
desirable to fail the hot-removal operation in a graceful way
rather than to crash the kernel, but currenty a success or a kernel
crash are the only possible outcomes of an attempted memory
hot-removal. Needless to say, that is not a very attractive
alternative and it had to be addressed.
However, in order to make it work for memory, I first had to make
it work for CPUs and for this purpose I needed to modify the ACPI
processor driver. It's been split into two parts, a resident one
handling the low-level initialization/cleanup and a modular one
playing the actual driver's role (but it binds to the CPU system
device objects rather than to the ACPI device objects representing
processors). That's been sort of like a live brain surgery on a
patient who's riding a bike.
So this is a little scary, but since we found and fixed a couple of
regressions it caused to happen during the early linux-next testing
(a month ago), nobody has complained.
As a bonus we remove some duplicated ACPI hotplug code, because the
ACPI-based CPU hotplug is now going to use the common ACPI hotplug
code.
- Lighter weight freezing of tasks.
These changes from Colin Cross and Mandeep Singh Baines are
targeted at making the freezing of tasks a bit less heavy weight
operation. They reduce the number of tasks woken up every time
during the freezing, by using the observation that the freezer
simply doesn't need to wake up some of them and wait for them all
to call refrigerator(). The time needed for the freezer to decide
to report a failure is reduced too.
Also reintroduced is the check causing a lockdep warining to
trigger when try_to_freeze() is called with locks held (which is
generally unsafe and shouldn't happen).
- cpufreq updates
First off, a commit from Srivatsa S Bhat fixes a resume regression
introduced during the 3.10 cycle causing some cpufreq sysfs
attributes to return wrong values to user space after resume. The
fix is kind of fresh, but also it's pretty obvious once Srivatsa
has identified the root cause.
Second, we have a new freqdomain_cpus sysfs attribute for the
acpi-cpufreq driver to provide information previously available via
related_cpus. From Lan Tianyu.
Finally, we fix a number of issues, mostly related to the
CPUFREQ_POSTCHANGE notifier and cpufreq Kconfig options and clean
up some code. The majority of changes from Viresh Kumar with bits
from Jacob Shin, Heiko Stübner, Xiaoguang Chen, Ezequiel Garcia,
Arnd Bergmann, and Tang Yuantian.
- ACPICA update
A usual bunch of updates from the ACPICA upstream.
During the 3.4 cycle we introduced support for ACPI 5 extended
sleep registers, but they are only supposed to be used if the
HW-reduced mode bit is set in the FADT flags and the code attempted
to use them without checking that bit. That caused suspend/resume
regressions to happen on some systems. Fix from Lv Zheng causes
those registers to be used only if the HW-reduced mode bit is set.
Apart from this some other ACPICA bugs are fixed and code cleanups
are made by Bob Moore, Tomasz Nowicki, Lv Zheng, Chao Guan, and
Zhang Rui.
- cpuidle updates
New driver for Xilinx Zynq processors is added by Michal Simek.
Multidriver support simplification, addition of some missing
kerneldoc comments and Kconfig-related fixes come from Daniel
Lezcano.
- ACPI power management updates
Changes to make suspend/resume work correctly in Xen guests from
Konrad Rzeszutek Wilk, sparse warning fix from Fengguang Wu and
cleanups and fixes of the ACPI device power state selection
routine.
- ACPI documentation updates
Some previously missing pieces of ACPI documentation are added by
Lv Zheng and Aaron Lu (hopefully, that will help people to
uderstand how the ACPI subsystem works) and one outdated doc is
updated by Hanjun Guo.
- Assorted ACPI updates
We finally nailed down the IA-64 issue that was the reason for
reverting commit 9f29ab11dd ("ACPI / scan: do not match drivers
against objects having scan handlers"), so we can fix it and move
the ACPI scan handler check added to the ACPI video driver back to
the core.
A mechanism for adding CMOS RTC address space handlers is
introduced by Lan Tianyu to allow some EC-related breakage to be
fixed on some systems.
A spec-compliant implementation of acpi_os_get_timer() is added by
Mika Westerberg.
The evaluation of _STA is added to do_acpi_find_child() to avoid
situations in which a pointer to a disabled device object is
returned instead of an enabled one with the same _ADR value. From
Jeff Wu.
Intel BayTrail PCH (Platform Controller Hub) support is added to
the ACPI driver for Intel Low-Power Subsystems (LPSS) and that
driver is modified to work around a couple of known BIOS issues.
Changes from Mika Westerberg and Heikki Krogerus.
The EC driver is fixed by Vasiliy Kulikov to use get_user() and
put_user() instead of dereferencing user space pointers blindly.
Code cleanups are made by Bjorn Helgaas, Nicholas Mazzuca and Toshi
Kani.
- Assorted power management updates
The "runtime idle" helper routine is changed to take the return
values of the callbacks executed by it into account and to call
rpm_suspend() if they return 0, which allows us to reduce the
overall code bloat a bit (by dropping some code that's not
necessary any more after that modification).
The runtime PM documentation is updated by Alan Stern (to reflect
the "runtime idle" behavior change).
New trace points for PM QoS are added by Sahara
(<keun-o.park@windriver.com>).
PM QoS documentation is updated by Lan Tianyu.
Code cleanups are made and minor issues are addressed by Bernie
Thompson, Bjorn Helgaas, Julius Werner, and Shuah Khan.
- devfreq updates
New driver for the Exynos5-bus device from Abhilash Kesavan.
Minor cleanups, fixes and MAINTAINERS update from MyungJoo Ham,
Abhilash Kesavan, Paul Bolle, Rajagopal Venkat, and Wei Yongjun.
- OMAP power management updates
Adaptive Voltage Scaling (AVS) SmartReflex voltage control driver
updates from Andrii Tseglytskyi and Nishanth Menon."
* tag 'pm+acpi-3.11-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm: (162 commits)
cpufreq: Fix cpufreq regression after suspend/resume
ACPI / PM: Fix possible NULL pointer deref in acpi_pm_device_sleep_state()
PM / Sleep: Warn about system time after resume with pm_trace
cpufreq: don't leave stale policy pointer in cdbs->cur_policy
acpi-cpufreq: Add new sysfs attribute freqdomain_cpus
cpufreq: make sure frequency transitions are serialized
ACPI: implement acpi_os_get_timer() according the spec
ACPI / EC: Add HP Folio 13 to ec_dmi_table in order to skip DSDT scan
ACPI: Add CMOS RTC Operation Region handler support
ACPI / processor: Drop unused variable from processor_perflib.c
cpufreq: tegra: call CPUFREQ_POSTCHANGE notfier in error cases
cpufreq: s3c64xx: call CPUFREQ_POSTCHANGE notfier in error cases
cpufreq: omap: call CPUFREQ_POSTCHANGE notfier in error cases
cpufreq: imx6q: call CPUFREQ_POSTCHANGE notfier in error cases
cpufreq: exynos: call CPUFREQ_POSTCHANGE notfier in error cases
cpufreq: dbx500: call CPUFREQ_POSTCHANGE notfier in error cases
cpufreq: davinci: call CPUFREQ_POSTCHANGE notfier in error cases
cpufreq: arm-big-little: call CPUFREQ_POSTCHANGE notfier in error cases
cpufreq: powernow-k8: call CPUFREQ_POSTCHANGE notfier in error cases
cpufreq: pcc: call CPUFREQ_POSTCHANGE notfier in error cases
...
1062 lines
26 KiB
C
1062 lines
26 KiB
C
/*
|
|
* fs/cifs/transport.c
|
|
*
|
|
* Copyright (C) International Business Machines Corp., 2002,2008
|
|
* Author(s): Steve French (sfrench@us.ibm.com)
|
|
* Jeremy Allison (jra@samba.org) 2006.
|
|
*
|
|
* This library is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published
|
|
* by the Free Software Foundation; either version 2.1 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
|
* the GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <linux/fs.h>
|
|
#include <linux/list.h>
|
|
#include <linux/gfp.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/net.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/freezer.h>
|
|
#include <linux/tcp.h>
|
|
#include <linux/highmem.h>
|
|
#include <asm/uaccess.h>
|
|
#include <asm/processor.h>
|
|
#include <linux/mempool.h>
|
|
#include "cifspdu.h"
|
|
#include "cifsglob.h"
|
|
#include "cifsproto.h"
|
|
#include "cifs_debug.h"
|
|
|
|
void
|
|
cifs_wake_up_task(struct mid_q_entry *mid)
|
|
{
|
|
wake_up_process(mid->callback_data);
|
|
}
|
|
|
|
struct mid_q_entry *
|
|
AllocMidQEntry(const struct smb_hdr *smb_buffer, struct TCP_Server_Info *server)
|
|
{
|
|
struct mid_q_entry *temp;
|
|
|
|
if (server == NULL) {
|
|
cifs_dbg(VFS, "Null TCP session in AllocMidQEntry\n");
|
|
return NULL;
|
|
}
|
|
|
|
temp = mempool_alloc(cifs_mid_poolp, GFP_NOFS);
|
|
if (temp == NULL)
|
|
return temp;
|
|
else {
|
|
memset(temp, 0, sizeof(struct mid_q_entry));
|
|
temp->mid = smb_buffer->Mid; /* always LE */
|
|
temp->pid = current->pid;
|
|
temp->command = cpu_to_le16(smb_buffer->Command);
|
|
cifs_dbg(FYI, "For smb_command %d\n", smb_buffer->Command);
|
|
/* do_gettimeofday(&temp->when_sent);*/ /* easier to use jiffies */
|
|
/* when mid allocated can be before when sent */
|
|
temp->when_alloc = jiffies;
|
|
temp->server = server;
|
|
|
|
/*
|
|
* The default is for the mid to be synchronous, so the
|
|
* default callback just wakes up the current task.
|
|
*/
|
|
temp->callback = cifs_wake_up_task;
|
|
temp->callback_data = current;
|
|
}
|
|
|
|
atomic_inc(&midCount);
|
|
temp->mid_state = MID_REQUEST_ALLOCATED;
|
|
return temp;
|
|
}
|
|
|
|
void
|
|
DeleteMidQEntry(struct mid_q_entry *midEntry)
|
|
{
|
|
#ifdef CONFIG_CIFS_STATS2
|
|
__le16 command = midEntry->server->vals->lock_cmd;
|
|
unsigned long now;
|
|
#endif
|
|
midEntry->mid_state = MID_FREE;
|
|
atomic_dec(&midCount);
|
|
if (midEntry->large_buf)
|
|
cifs_buf_release(midEntry->resp_buf);
|
|
else
|
|
cifs_small_buf_release(midEntry->resp_buf);
|
|
#ifdef CONFIG_CIFS_STATS2
|
|
now = jiffies;
|
|
/* commands taking longer than one second are indications that
|
|
something is wrong, unless it is quite a slow link or server */
|
|
if ((now - midEntry->when_alloc) > HZ) {
|
|
if ((cifsFYI & CIFS_TIMER) && (midEntry->command != command)) {
|
|
printk(KERN_DEBUG " CIFS slow rsp: cmd %d mid %llu",
|
|
midEntry->command, midEntry->mid);
|
|
printk(" A: 0x%lx S: 0x%lx R: 0x%lx\n",
|
|
now - midEntry->when_alloc,
|
|
now - midEntry->when_sent,
|
|
now - midEntry->when_received);
|
|
}
|
|
}
|
|
#endif
|
|
mempool_free(midEntry, cifs_mid_poolp);
|
|
}
|
|
|
|
void
|
|
cifs_delete_mid(struct mid_q_entry *mid)
|
|
{
|
|
spin_lock(&GlobalMid_Lock);
|
|
list_del(&mid->qhead);
|
|
spin_unlock(&GlobalMid_Lock);
|
|
|
|
DeleteMidQEntry(mid);
|
|
}
|
|
|
|
/*
|
|
* smb_send_kvec - send an array of kvecs to the server
|
|
* @server: Server to send the data to
|
|
* @iov: Pointer to array of kvecs
|
|
* @n_vec: length of kvec array
|
|
* @sent: amount of data sent on socket is stored here
|
|
*
|
|
* Our basic "send data to server" function. Should be called with srv_mutex
|
|
* held. The caller is responsible for handling the results.
|
|
*/
|
|
static int
|
|
smb_send_kvec(struct TCP_Server_Info *server, struct kvec *iov, size_t n_vec,
|
|
size_t *sent)
|
|
{
|
|
int rc = 0;
|
|
int i = 0;
|
|
struct msghdr smb_msg;
|
|
unsigned int remaining;
|
|
size_t first_vec = 0;
|
|
struct socket *ssocket = server->ssocket;
|
|
|
|
*sent = 0;
|
|
|
|
smb_msg.msg_name = (struct sockaddr *) &server->dstaddr;
|
|
smb_msg.msg_namelen = sizeof(struct sockaddr);
|
|
smb_msg.msg_control = NULL;
|
|
smb_msg.msg_controllen = 0;
|
|
if (server->noblocksnd)
|
|
smb_msg.msg_flags = MSG_DONTWAIT + MSG_NOSIGNAL;
|
|
else
|
|
smb_msg.msg_flags = MSG_NOSIGNAL;
|
|
|
|
remaining = 0;
|
|
for (i = 0; i < n_vec; i++)
|
|
remaining += iov[i].iov_len;
|
|
|
|
i = 0;
|
|
while (remaining) {
|
|
/*
|
|
* If blocking send, we try 3 times, since each can block
|
|
* for 5 seconds. For nonblocking we have to try more
|
|
* but wait increasing amounts of time allowing time for
|
|
* socket to clear. The overall time we wait in either
|
|
* case to send on the socket is about 15 seconds.
|
|
* Similarly we wait for 15 seconds for a response from
|
|
* the server in SendReceive[2] for the server to send
|
|
* a response back for most types of requests (except
|
|
* SMB Write past end of file which can be slow, and
|
|
* blocking lock operations). NFS waits slightly longer
|
|
* than CIFS, but this can make it take longer for
|
|
* nonresponsive servers to be detected and 15 seconds
|
|
* is more than enough time for modern networks to
|
|
* send a packet. In most cases if we fail to send
|
|
* after the retries we will kill the socket and
|
|
* reconnect which may clear the network problem.
|
|
*/
|
|
rc = kernel_sendmsg(ssocket, &smb_msg, &iov[first_vec],
|
|
n_vec - first_vec, remaining);
|
|
if (rc == -EAGAIN) {
|
|
i++;
|
|
if (i >= 14 || (!server->noblocksnd && (i > 2))) {
|
|
cifs_dbg(VFS, "sends on sock %p stuck for 15 seconds\n",
|
|
ssocket);
|
|
rc = -EAGAIN;
|
|
break;
|
|
}
|
|
msleep(1 << i);
|
|
continue;
|
|
}
|
|
|
|
if (rc < 0)
|
|
break;
|
|
|
|
/* send was at least partially successful */
|
|
*sent += rc;
|
|
|
|
if (rc == remaining) {
|
|
remaining = 0;
|
|
break;
|
|
}
|
|
|
|
if (rc > remaining) {
|
|
cifs_dbg(VFS, "sent %d requested %d\n", rc, remaining);
|
|
break;
|
|
}
|
|
|
|
if (rc == 0) {
|
|
/* should never happen, letting socket clear before
|
|
retrying is our only obvious option here */
|
|
cifs_dbg(VFS, "tcp sent no data\n");
|
|
msleep(500);
|
|
continue;
|
|
}
|
|
|
|
remaining -= rc;
|
|
|
|
/* the line below resets i */
|
|
for (i = first_vec; i < n_vec; i++) {
|
|
if (iov[i].iov_len) {
|
|
if (rc > iov[i].iov_len) {
|
|
rc -= iov[i].iov_len;
|
|
iov[i].iov_len = 0;
|
|
} else {
|
|
iov[i].iov_base += rc;
|
|
iov[i].iov_len -= rc;
|
|
first_vec = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
i = 0; /* in case we get ENOSPC on the next send */
|
|
rc = 0;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* rqst_page_to_kvec - Turn a slot in the smb_rqst page array into a kvec
|
|
* @rqst: pointer to smb_rqst
|
|
* @idx: index into the array of the page
|
|
* @iov: pointer to struct kvec that will hold the result
|
|
*
|
|
* Helper function to convert a slot in the rqst->rq_pages array into a kvec.
|
|
* The page will be kmapped and the address placed into iov_base. The length
|
|
* will then be adjusted according to the ptailoff.
|
|
*/
|
|
void
|
|
cifs_rqst_page_to_kvec(struct smb_rqst *rqst, unsigned int idx,
|
|
struct kvec *iov)
|
|
{
|
|
/*
|
|
* FIXME: We could avoid this kmap altogether if we used
|
|
* kernel_sendpage instead of kernel_sendmsg. That will only
|
|
* work if signing is disabled though as sendpage inlines the
|
|
* page directly into the fraglist. If userspace modifies the
|
|
* page after we calculate the signature, then the server will
|
|
* reject it and may break the connection. kernel_sendmsg does
|
|
* an extra copy of the data and avoids that issue.
|
|
*/
|
|
iov->iov_base = kmap(rqst->rq_pages[idx]);
|
|
|
|
/* if last page, don't send beyond this offset into page */
|
|
if (idx == (rqst->rq_npages - 1))
|
|
iov->iov_len = rqst->rq_tailsz;
|
|
else
|
|
iov->iov_len = rqst->rq_pagesz;
|
|
}
|
|
|
|
static int
|
|
smb_send_rqst(struct TCP_Server_Info *server, struct smb_rqst *rqst)
|
|
{
|
|
int rc;
|
|
struct kvec *iov = rqst->rq_iov;
|
|
int n_vec = rqst->rq_nvec;
|
|
unsigned int smb_buf_length = get_rfc1002_length(iov[0].iov_base);
|
|
unsigned int i;
|
|
size_t total_len = 0, sent;
|
|
struct socket *ssocket = server->ssocket;
|
|
int val = 1;
|
|
|
|
if (ssocket == NULL)
|
|
return -ENOTSOCK;
|
|
|
|
cifs_dbg(FYI, "Sending smb: smb_len=%u\n", smb_buf_length);
|
|
dump_smb(iov[0].iov_base, iov[0].iov_len);
|
|
|
|
/* cork the socket */
|
|
kernel_setsockopt(ssocket, SOL_TCP, TCP_CORK,
|
|
(char *)&val, sizeof(val));
|
|
|
|
rc = smb_send_kvec(server, iov, n_vec, &sent);
|
|
if (rc < 0)
|
|
goto uncork;
|
|
|
|
total_len += sent;
|
|
|
|
/* now walk the page array and send each page in it */
|
|
for (i = 0; i < rqst->rq_npages; i++) {
|
|
struct kvec p_iov;
|
|
|
|
cifs_rqst_page_to_kvec(rqst, i, &p_iov);
|
|
rc = smb_send_kvec(server, &p_iov, 1, &sent);
|
|
kunmap(rqst->rq_pages[i]);
|
|
if (rc < 0)
|
|
break;
|
|
|
|
total_len += sent;
|
|
}
|
|
|
|
uncork:
|
|
/* uncork it */
|
|
val = 0;
|
|
kernel_setsockopt(ssocket, SOL_TCP, TCP_CORK,
|
|
(char *)&val, sizeof(val));
|
|
|
|
if ((total_len > 0) && (total_len != smb_buf_length + 4)) {
|
|
cifs_dbg(FYI, "partial send (wanted=%u sent=%zu): terminating session\n",
|
|
smb_buf_length + 4, total_len);
|
|
/*
|
|
* If we have only sent part of an SMB then the next SMB could
|
|
* be taken as the remainder of this one. We need to kill the
|
|
* socket so the server throws away the partial SMB
|
|
*/
|
|
server->tcpStatus = CifsNeedReconnect;
|
|
}
|
|
|
|
if (rc < 0 && rc != -EINTR)
|
|
cifs_dbg(VFS, "Error %d sending data on socket to server\n",
|
|
rc);
|
|
else
|
|
rc = 0;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
smb_sendv(struct TCP_Server_Info *server, struct kvec *iov, int n_vec)
|
|
{
|
|
struct smb_rqst rqst = { .rq_iov = iov,
|
|
.rq_nvec = n_vec };
|
|
|
|
return smb_send_rqst(server, &rqst);
|
|
}
|
|
|
|
int
|
|
smb_send(struct TCP_Server_Info *server, struct smb_hdr *smb_buffer,
|
|
unsigned int smb_buf_length)
|
|
{
|
|
struct kvec iov;
|
|
|
|
iov.iov_base = smb_buffer;
|
|
iov.iov_len = smb_buf_length + 4;
|
|
|
|
return smb_sendv(server, &iov, 1);
|
|
}
|
|
|
|
static int
|
|
wait_for_free_credits(struct TCP_Server_Info *server, const int timeout,
|
|
int *credits)
|
|
{
|
|
int rc;
|
|
|
|
spin_lock(&server->req_lock);
|
|
if (timeout == CIFS_ASYNC_OP) {
|
|
/* oplock breaks must not be held up */
|
|
server->in_flight++;
|
|
*credits -= 1;
|
|
spin_unlock(&server->req_lock);
|
|
return 0;
|
|
}
|
|
|
|
while (1) {
|
|
if (*credits <= 0) {
|
|
spin_unlock(&server->req_lock);
|
|
cifs_num_waiters_inc(server);
|
|
rc = wait_event_killable(server->request_q,
|
|
has_credits(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;
|
|
}
|
|
|
|
/*
|
|
* Can not count locking commands against total
|
|
* as they are allowed to block on server.
|
|
*/
|
|
|
|
/* update # of requests on the wire to server */
|
|
if (timeout != CIFS_BLOCKING_OP) {
|
|
*credits -= 1;
|
|
server->in_flight++;
|
|
}
|
|
spin_unlock(&server->req_lock);
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
wait_for_free_request(struct TCP_Server_Info *server, const int timeout,
|
|
const int optype)
|
|
{
|
|
return wait_for_free_credits(server, timeout,
|
|
server->ops->get_credits_field(server, optype));
|
|
}
|
|
|
|
static int allocate_mid(struct cifs_ses *ses, struct smb_hdr *in_buf,
|
|
struct mid_q_entry **ppmidQ)
|
|
{
|
|
if (ses->server->tcpStatus == CifsExiting) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (ses->server->tcpStatus == CifsNeedReconnect) {
|
|
cifs_dbg(FYI, "tcp session dead - return to caller to retry\n");
|
|
return -EAGAIN;
|
|
}
|
|
|
|
if (ses->status != CifsGood) {
|
|
/* check if SMB session is bad because we are setting it up */
|
|
if ((in_buf->Command != SMB_COM_SESSION_SETUP_ANDX) &&
|
|
(in_buf->Command != SMB_COM_NEGOTIATE))
|
|
return -EAGAIN;
|
|
/* else ok - we are setting up session */
|
|
}
|
|
*ppmidQ = AllocMidQEntry(in_buf, ses->server);
|
|
if (*ppmidQ == NULL)
|
|
return -ENOMEM;
|
|
spin_lock(&GlobalMid_Lock);
|
|
list_add_tail(&(*ppmidQ)->qhead, &ses->server->pending_mid_q);
|
|
spin_unlock(&GlobalMid_Lock);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
wait_for_response(struct TCP_Server_Info *server, struct mid_q_entry *midQ)
|
|
{
|
|
int error;
|
|
|
|
error = wait_event_freezekillable_unsafe(server->response_q,
|
|
midQ->mid_state != MID_REQUEST_SUBMITTED);
|
|
if (error < 0)
|
|
return -ERESTARTSYS;
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct mid_q_entry *
|
|
cifs_setup_async_request(struct TCP_Server_Info *server, struct smb_rqst *rqst)
|
|
{
|
|
int rc;
|
|
struct smb_hdr *hdr = (struct smb_hdr *)rqst->rq_iov[0].iov_base;
|
|
struct mid_q_entry *mid;
|
|
|
|
/* enable signing if server requires it */
|
|
if (server->sign)
|
|
hdr->Flags2 |= SMBFLG2_SECURITY_SIGNATURE;
|
|
|
|
mid = AllocMidQEntry(hdr, server);
|
|
if (mid == NULL)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
rc = cifs_sign_rqst(rqst, server, &mid->sequence_number);
|
|
if (rc) {
|
|
DeleteMidQEntry(mid);
|
|
return ERR_PTR(rc);
|
|
}
|
|
|
|
return mid;
|
|
}
|
|
|
|
/*
|
|
* Send a SMB request and set the callback function in the mid to handle
|
|
* the result. Caller is responsible for dealing with timeouts.
|
|
*/
|
|
int
|
|
cifs_call_async(struct TCP_Server_Info *server, struct smb_rqst *rqst,
|
|
mid_receive_t *receive, mid_callback_t *callback,
|
|
void *cbdata, const int flags)
|
|
{
|
|
int rc, timeout, optype;
|
|
struct mid_q_entry *mid;
|
|
|
|
timeout = flags & CIFS_TIMEOUT_MASK;
|
|
optype = flags & CIFS_OP_MASK;
|
|
|
|
rc = wait_for_free_request(server, timeout, optype);
|
|
if (rc)
|
|
return rc;
|
|
|
|
mutex_lock(&server->srv_mutex);
|
|
mid = server->ops->setup_async_request(server, rqst);
|
|
if (IS_ERR(mid)) {
|
|
mutex_unlock(&server->srv_mutex);
|
|
add_credits(server, 1, optype);
|
|
wake_up(&server->request_q);
|
|
return PTR_ERR(mid);
|
|
}
|
|
|
|
mid->receive = receive;
|
|
mid->callback = callback;
|
|
mid->callback_data = cbdata;
|
|
mid->mid_state = MID_REQUEST_SUBMITTED;
|
|
|
|
/* put it on the pending_mid_q */
|
|
spin_lock(&GlobalMid_Lock);
|
|
list_add_tail(&mid->qhead, &server->pending_mid_q);
|
|
spin_unlock(&GlobalMid_Lock);
|
|
|
|
|
|
cifs_in_send_inc(server);
|
|
rc = smb_send_rqst(server, rqst);
|
|
cifs_in_send_dec(server);
|
|
cifs_save_when_sent(mid);
|
|
|
|
if (rc < 0)
|
|
server->sequence_number -= 2;
|
|
mutex_unlock(&server->srv_mutex);
|
|
|
|
if (rc == 0)
|
|
return 0;
|
|
|
|
cifs_delete_mid(mid);
|
|
add_credits(server, 1, optype);
|
|
wake_up(&server->request_q);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
*
|
|
* Send an SMB Request. No response info (other than return code)
|
|
* needs to be parsed.
|
|
*
|
|
* flags indicate the type of request buffer and how long to wait
|
|
* and whether to log NT STATUS code (error) before mapping it to POSIX error
|
|
*
|
|
*/
|
|
int
|
|
SendReceiveNoRsp(const unsigned int xid, struct cifs_ses *ses,
|
|
char *in_buf, int flags)
|
|
{
|
|
int rc;
|
|
struct kvec iov[1];
|
|
int resp_buf_type;
|
|
|
|
iov[0].iov_base = in_buf;
|
|
iov[0].iov_len = get_rfc1002_length(in_buf) + 4;
|
|
flags |= CIFS_NO_RESP;
|
|
rc = SendReceive2(xid, ses, iov, 1, &resp_buf_type, flags);
|
|
cifs_dbg(NOISY, "SendRcvNoRsp flags %d rc %d\n", flags, rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
cifs_sync_mid_result(struct mid_q_entry *mid, struct TCP_Server_Info *server)
|
|
{
|
|
int rc = 0;
|
|
|
|
cifs_dbg(FYI, "%s: cmd=%d mid=%llu state=%d\n",
|
|
__func__, le16_to_cpu(mid->command), mid->mid, mid->mid_state);
|
|
|
|
spin_lock(&GlobalMid_Lock);
|
|
switch (mid->mid_state) {
|
|
case MID_RESPONSE_RECEIVED:
|
|
spin_unlock(&GlobalMid_Lock);
|
|
return rc;
|
|
case MID_RETRY_NEEDED:
|
|
rc = -EAGAIN;
|
|
break;
|
|
case MID_RESPONSE_MALFORMED:
|
|
rc = -EIO;
|
|
break;
|
|
case MID_SHUTDOWN:
|
|
rc = -EHOSTDOWN;
|
|
break;
|
|
default:
|
|
list_del_init(&mid->qhead);
|
|
cifs_dbg(VFS, "%s: invalid mid state mid=%llu state=%d\n",
|
|
__func__, mid->mid, mid->mid_state);
|
|
rc = -EIO;
|
|
}
|
|
spin_unlock(&GlobalMid_Lock);
|
|
|
|
DeleteMidQEntry(mid);
|
|
return rc;
|
|
}
|
|
|
|
static inline int
|
|
send_cancel(struct TCP_Server_Info *server, void *buf, struct mid_q_entry *mid)
|
|
{
|
|
return server->ops->send_cancel ?
|
|
server->ops->send_cancel(server, buf, mid) : 0;
|
|
}
|
|
|
|
int
|
|
cifs_check_receive(struct mid_q_entry *mid, struct TCP_Server_Info *server,
|
|
bool log_error)
|
|
{
|
|
unsigned int len = get_rfc1002_length(mid->resp_buf) + 4;
|
|
|
|
dump_smb(mid->resp_buf, min_t(u32, 92, len));
|
|
|
|
/* convert the length into a more usable form */
|
|
if (server->sign) {
|
|
struct kvec iov;
|
|
int rc = 0;
|
|
struct smb_rqst rqst = { .rq_iov = &iov,
|
|
.rq_nvec = 1 };
|
|
|
|
iov.iov_base = mid->resp_buf;
|
|
iov.iov_len = len;
|
|
/* FIXME: add code to kill session */
|
|
rc = cifs_verify_signature(&rqst, server,
|
|
mid->sequence_number);
|
|
if (rc)
|
|
cifs_dbg(VFS, "SMB signature verification returned error = %d\n",
|
|
rc);
|
|
}
|
|
|
|
/* BB special case reconnect tid and uid here? */
|
|
return map_smb_to_linux_error(mid->resp_buf, log_error);
|
|
}
|
|
|
|
struct mid_q_entry *
|
|
cifs_setup_request(struct cifs_ses *ses, struct smb_rqst *rqst)
|
|
{
|
|
int rc;
|
|
struct smb_hdr *hdr = (struct smb_hdr *)rqst->rq_iov[0].iov_base;
|
|
struct mid_q_entry *mid;
|
|
|
|
rc = allocate_mid(ses, hdr, &mid);
|
|
if (rc)
|
|
return ERR_PTR(rc);
|
|
rc = cifs_sign_rqst(rqst, ses->server, &mid->sequence_number);
|
|
if (rc) {
|
|
cifs_delete_mid(mid);
|
|
return ERR_PTR(rc);
|
|
}
|
|
return mid;
|
|
}
|
|
|
|
int
|
|
SendReceive2(const unsigned int xid, struct cifs_ses *ses,
|
|
struct kvec *iov, int n_vec, int *resp_buf_type /* ret */,
|
|
const int flags)
|
|
{
|
|
int rc = 0;
|
|
int timeout, optype;
|
|
struct mid_q_entry *midQ;
|
|
char *buf = iov[0].iov_base;
|
|
unsigned int credits = 1;
|
|
struct smb_rqst rqst = { .rq_iov = iov,
|
|
.rq_nvec = n_vec };
|
|
|
|
timeout = flags & CIFS_TIMEOUT_MASK;
|
|
optype = flags & CIFS_OP_MASK;
|
|
|
|
*resp_buf_type = CIFS_NO_BUFFER; /* no response buf yet */
|
|
|
|
if ((ses == NULL) || (ses->server == NULL)) {
|
|
cifs_small_buf_release(buf);
|
|
cifs_dbg(VFS, "Null session\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if (ses->server->tcpStatus == CifsExiting) {
|
|
cifs_small_buf_release(buf);
|
|
return -ENOENT;
|
|
}
|
|
|
|
/*
|
|
* Ensure that we do not send more than 50 overlapping requests
|
|
* to the same server. We may make this configurable later or
|
|
* use ses->maxReq.
|
|
*/
|
|
|
|
rc = wait_for_free_request(ses->server, timeout, optype);
|
|
if (rc) {
|
|
cifs_small_buf_release(buf);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Make sure that we sign in the same order that we send on this socket
|
|
* and avoid races inside tcp sendmsg code that could cause corruption
|
|
* of smb data.
|
|
*/
|
|
|
|
mutex_lock(&ses->server->srv_mutex);
|
|
|
|
midQ = ses->server->ops->setup_request(ses, &rqst);
|
|
if (IS_ERR(midQ)) {
|
|
mutex_unlock(&ses->server->srv_mutex);
|
|
cifs_small_buf_release(buf);
|
|
/* Update # of requests on wire to server */
|
|
add_credits(ses->server, 1, optype);
|
|
return PTR_ERR(midQ);
|
|
}
|
|
|
|
midQ->mid_state = MID_REQUEST_SUBMITTED;
|
|
cifs_in_send_inc(ses->server);
|
|
rc = smb_sendv(ses->server, iov, n_vec);
|
|
cifs_in_send_dec(ses->server);
|
|
cifs_save_when_sent(midQ);
|
|
|
|
if (rc < 0)
|
|
ses->server->sequence_number -= 2;
|
|
mutex_unlock(&ses->server->srv_mutex);
|
|
|
|
if (rc < 0) {
|
|
cifs_small_buf_release(buf);
|
|
goto out;
|
|
}
|
|
|
|
if (timeout == CIFS_ASYNC_OP) {
|
|
cifs_small_buf_release(buf);
|
|
goto out;
|
|
}
|
|
|
|
rc = wait_for_response(ses->server, midQ);
|
|
if (rc != 0) {
|
|
send_cancel(ses->server, buf, midQ);
|
|
spin_lock(&GlobalMid_Lock);
|
|
if (midQ->mid_state == MID_REQUEST_SUBMITTED) {
|
|
midQ->callback = DeleteMidQEntry;
|
|
spin_unlock(&GlobalMid_Lock);
|
|
cifs_small_buf_release(buf);
|
|
add_credits(ses->server, 1, optype);
|
|
return rc;
|
|
}
|
|
spin_unlock(&GlobalMid_Lock);
|
|
}
|
|
|
|
cifs_small_buf_release(buf);
|
|
|
|
rc = cifs_sync_mid_result(midQ, ses->server);
|
|
if (rc != 0) {
|
|
add_credits(ses->server, 1, optype);
|
|
return rc;
|
|
}
|
|
|
|
if (!midQ->resp_buf || midQ->mid_state != MID_RESPONSE_RECEIVED) {
|
|
rc = -EIO;
|
|
cifs_dbg(FYI, "Bad MID state?\n");
|
|
goto out;
|
|
}
|
|
|
|
buf = (char *)midQ->resp_buf;
|
|
iov[0].iov_base = buf;
|
|
iov[0].iov_len = get_rfc1002_length(buf) + 4;
|
|
if (midQ->large_buf)
|
|
*resp_buf_type = CIFS_LARGE_BUFFER;
|
|
else
|
|
*resp_buf_type = CIFS_SMALL_BUFFER;
|
|
|
|
credits = ses->server->ops->get_credits(midQ);
|
|
|
|
rc = ses->server->ops->check_receive(midQ, ses->server,
|
|
flags & CIFS_LOG_ERROR);
|
|
|
|
/* mark it so buf will not be freed by cifs_delete_mid */
|
|
if ((flags & CIFS_NO_RESP) == 0)
|
|
midQ->resp_buf = NULL;
|
|
out:
|
|
cifs_delete_mid(midQ);
|
|
add_credits(ses->server, credits, optype);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int
|
|
SendReceive(const unsigned int xid, struct cifs_ses *ses,
|
|
struct smb_hdr *in_buf, struct smb_hdr *out_buf,
|
|
int *pbytes_returned, const int timeout)
|
|
{
|
|
int rc = 0;
|
|
struct mid_q_entry *midQ;
|
|
|
|
if (ses == NULL) {
|
|
cifs_dbg(VFS, "Null smb session\n");
|
|
return -EIO;
|
|
}
|
|
if (ses->server == NULL) {
|
|
cifs_dbg(VFS, "Null tcp session\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if (ses->server->tcpStatus == CifsExiting)
|
|
return -ENOENT;
|
|
|
|
/* Ensure that we do not send more than 50 overlapping requests
|
|
to the same server. We may make this configurable later or
|
|
use ses->maxReq */
|
|
|
|
if (be32_to_cpu(in_buf->smb_buf_length) > CIFSMaxBufSize +
|
|
MAX_CIFS_HDR_SIZE - 4) {
|
|
cifs_dbg(VFS, "Illegal length, greater than maximum frame, %d\n",
|
|
be32_to_cpu(in_buf->smb_buf_length));
|
|
return -EIO;
|
|
}
|
|
|
|
rc = wait_for_free_request(ses->server, timeout, 0);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* make sure that we sign in the same order that we send on this socket
|
|
and avoid races inside tcp sendmsg code that could cause corruption
|
|
of smb data */
|
|
|
|
mutex_lock(&ses->server->srv_mutex);
|
|
|
|
rc = allocate_mid(ses, in_buf, &midQ);
|
|
if (rc) {
|
|
mutex_unlock(&ses->server->srv_mutex);
|
|
/* Update # of requests on wire to server */
|
|
add_credits(ses->server, 1, 0);
|
|
return rc;
|
|
}
|
|
|
|
rc = cifs_sign_smb(in_buf, ses->server, &midQ->sequence_number);
|
|
if (rc) {
|
|
mutex_unlock(&ses->server->srv_mutex);
|
|
goto out;
|
|
}
|
|
|
|
midQ->mid_state = MID_REQUEST_SUBMITTED;
|
|
|
|
cifs_in_send_inc(ses->server);
|
|
rc = smb_send(ses->server, in_buf, be32_to_cpu(in_buf->smb_buf_length));
|
|
cifs_in_send_dec(ses->server);
|
|
cifs_save_when_sent(midQ);
|
|
|
|
if (rc < 0)
|
|
ses->server->sequence_number -= 2;
|
|
|
|
mutex_unlock(&ses->server->srv_mutex);
|
|
|
|
if (rc < 0)
|
|
goto out;
|
|
|
|
if (timeout == CIFS_ASYNC_OP)
|
|
goto out;
|
|
|
|
rc = wait_for_response(ses->server, midQ);
|
|
if (rc != 0) {
|
|
send_cancel(ses->server, in_buf, midQ);
|
|
spin_lock(&GlobalMid_Lock);
|
|
if (midQ->mid_state == MID_REQUEST_SUBMITTED) {
|
|
/* no longer considered to be "in-flight" */
|
|
midQ->callback = DeleteMidQEntry;
|
|
spin_unlock(&GlobalMid_Lock);
|
|
add_credits(ses->server, 1, 0);
|
|
return rc;
|
|
}
|
|
spin_unlock(&GlobalMid_Lock);
|
|
}
|
|
|
|
rc = cifs_sync_mid_result(midQ, ses->server);
|
|
if (rc != 0) {
|
|
add_credits(ses->server, 1, 0);
|
|
return rc;
|
|
}
|
|
|
|
if (!midQ->resp_buf || !out_buf ||
|
|
midQ->mid_state != MID_RESPONSE_RECEIVED) {
|
|
rc = -EIO;
|
|
cifs_dbg(VFS, "Bad MID state?\n");
|
|
goto out;
|
|
}
|
|
|
|
*pbytes_returned = get_rfc1002_length(midQ->resp_buf);
|
|
memcpy(out_buf, midQ->resp_buf, *pbytes_returned + 4);
|
|
rc = cifs_check_receive(midQ, ses->server, 0);
|
|
out:
|
|
cifs_delete_mid(midQ);
|
|
add_credits(ses->server, 1, 0);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* We send a LOCKINGX_CANCEL_LOCK to cause the Windows
|
|
blocking lock to return. */
|
|
|
|
static int
|
|
send_lock_cancel(const unsigned int xid, struct cifs_tcon *tcon,
|
|
struct smb_hdr *in_buf,
|
|
struct smb_hdr *out_buf)
|
|
{
|
|
int bytes_returned;
|
|
struct cifs_ses *ses = tcon->ses;
|
|
LOCK_REQ *pSMB = (LOCK_REQ *)in_buf;
|
|
|
|
/* We just modify the current in_buf to change
|
|
the type of lock from LOCKING_ANDX_SHARED_LOCK
|
|
or LOCKING_ANDX_EXCLUSIVE_LOCK to
|
|
LOCKING_ANDX_CANCEL_LOCK. */
|
|
|
|
pSMB->LockType = LOCKING_ANDX_CANCEL_LOCK|LOCKING_ANDX_LARGE_FILES;
|
|
pSMB->Timeout = 0;
|
|
pSMB->hdr.Mid = get_next_mid(ses->server);
|
|
|
|
return SendReceive(xid, ses, in_buf, out_buf,
|
|
&bytes_returned, 0);
|
|
}
|
|
|
|
int
|
|
SendReceiveBlockingLock(const unsigned int xid, struct cifs_tcon *tcon,
|
|
struct smb_hdr *in_buf, struct smb_hdr *out_buf,
|
|
int *pbytes_returned)
|
|
{
|
|
int rc = 0;
|
|
int rstart = 0;
|
|
struct mid_q_entry *midQ;
|
|
struct cifs_ses *ses;
|
|
|
|
if (tcon == NULL || tcon->ses == NULL) {
|
|
cifs_dbg(VFS, "Null smb session\n");
|
|
return -EIO;
|
|
}
|
|
ses = tcon->ses;
|
|
|
|
if (ses->server == NULL) {
|
|
cifs_dbg(VFS, "Null tcp session\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if (ses->server->tcpStatus == CifsExiting)
|
|
return -ENOENT;
|
|
|
|
/* Ensure that we do not send more than 50 overlapping requests
|
|
to the same server. We may make this configurable later or
|
|
use ses->maxReq */
|
|
|
|
if (be32_to_cpu(in_buf->smb_buf_length) > CIFSMaxBufSize +
|
|
MAX_CIFS_HDR_SIZE - 4) {
|
|
cifs_dbg(VFS, "Illegal length, greater than maximum frame, %d\n",
|
|
be32_to_cpu(in_buf->smb_buf_length));
|
|
return -EIO;
|
|
}
|
|
|
|
rc = wait_for_free_request(ses->server, CIFS_BLOCKING_OP, 0);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* make sure that we sign in the same order that we send on this socket
|
|
and avoid races inside tcp sendmsg code that could cause corruption
|
|
of smb data */
|
|
|
|
mutex_lock(&ses->server->srv_mutex);
|
|
|
|
rc = allocate_mid(ses, in_buf, &midQ);
|
|
if (rc) {
|
|
mutex_unlock(&ses->server->srv_mutex);
|
|
return rc;
|
|
}
|
|
|
|
rc = cifs_sign_smb(in_buf, ses->server, &midQ->sequence_number);
|
|
if (rc) {
|
|
cifs_delete_mid(midQ);
|
|
mutex_unlock(&ses->server->srv_mutex);
|
|
return rc;
|
|
}
|
|
|
|
midQ->mid_state = MID_REQUEST_SUBMITTED;
|
|
cifs_in_send_inc(ses->server);
|
|
rc = smb_send(ses->server, in_buf, be32_to_cpu(in_buf->smb_buf_length));
|
|
cifs_in_send_dec(ses->server);
|
|
cifs_save_when_sent(midQ);
|
|
|
|
if (rc < 0)
|
|
ses->server->sequence_number -= 2;
|
|
|
|
mutex_unlock(&ses->server->srv_mutex);
|
|
|
|
if (rc < 0) {
|
|
cifs_delete_mid(midQ);
|
|
return rc;
|
|
}
|
|
|
|
/* Wait for a reply - allow signals to interrupt. */
|
|
rc = wait_event_interruptible(ses->server->response_q,
|
|
(!(midQ->mid_state == MID_REQUEST_SUBMITTED)) ||
|
|
((ses->server->tcpStatus != CifsGood) &&
|
|
(ses->server->tcpStatus != CifsNew)));
|
|
|
|
/* Were we interrupted by a signal ? */
|
|
if ((rc == -ERESTARTSYS) &&
|
|
(midQ->mid_state == MID_REQUEST_SUBMITTED) &&
|
|
((ses->server->tcpStatus == CifsGood) ||
|
|
(ses->server->tcpStatus == CifsNew))) {
|
|
|
|
if (in_buf->Command == SMB_COM_TRANSACTION2) {
|
|
/* POSIX lock. We send a NT_CANCEL SMB to cause the
|
|
blocking lock to return. */
|
|
rc = send_cancel(ses->server, in_buf, midQ);
|
|
if (rc) {
|
|
cifs_delete_mid(midQ);
|
|
return rc;
|
|
}
|
|
} else {
|
|
/* Windows lock. We send a LOCKINGX_CANCEL_LOCK
|
|
to cause the blocking lock to return. */
|
|
|
|
rc = send_lock_cancel(xid, tcon, in_buf, out_buf);
|
|
|
|
/* If we get -ENOLCK back the lock may have
|
|
already been removed. Don't exit in this case. */
|
|
if (rc && rc != -ENOLCK) {
|
|
cifs_delete_mid(midQ);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
rc = wait_for_response(ses->server, midQ);
|
|
if (rc) {
|
|
send_cancel(ses->server, in_buf, midQ);
|
|
spin_lock(&GlobalMid_Lock);
|
|
if (midQ->mid_state == MID_REQUEST_SUBMITTED) {
|
|
/* no longer considered to be "in-flight" */
|
|
midQ->callback = DeleteMidQEntry;
|
|
spin_unlock(&GlobalMid_Lock);
|
|
return rc;
|
|
}
|
|
spin_unlock(&GlobalMid_Lock);
|
|
}
|
|
|
|
/* We got the response - restart system call. */
|
|
rstart = 1;
|
|
}
|
|
|
|
rc = cifs_sync_mid_result(midQ, ses->server);
|
|
if (rc != 0)
|
|
return rc;
|
|
|
|
/* rcvd frame is ok */
|
|
if (out_buf == NULL || midQ->mid_state != MID_RESPONSE_RECEIVED) {
|
|
rc = -EIO;
|
|
cifs_dbg(VFS, "Bad MID state?\n");
|
|
goto out;
|
|
}
|
|
|
|
*pbytes_returned = get_rfc1002_length(midQ->resp_buf);
|
|
memcpy(out_buf, midQ->resp_buf, *pbytes_returned + 4);
|
|
rc = cifs_check_receive(midQ, ses->server, 0);
|
|
out:
|
|
cifs_delete_mid(midQ);
|
|
if (rstart && rc == -EACCES)
|
|
return -ERESTARTSYS;
|
|
return rc;
|
|
}
|