mirror of
https://github.com/SCST-project/scst.git
synced 2026-05-21 04:31:26 +00:00
- Fix for a possible DoS, when misbehavine scst_user's handler hangs several memory allocations (= count of iSCSI read threads) and by so prevents other sessions from being served correctly. Spotted by Erez Zilber <erezzi.list@gmail.com>
- iSCSI read state machine cleanups - Fix a race when just freed iSCSI session accessed, because the corresponding SCST session is still being unregistered. git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@1224 d57e44dd-8a1f-0410-8b47-8ef2f437770f
This commit is contained in:
@@ -128,7 +128,7 @@ struct iscsi_conn *conn_lookup(struct iscsi_session *session, u16 cid)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void iscsi_make_conn_rd_active(struct iscsi_conn *conn)
|
||||
void iscsi_make_conn_rd_active(struct iscsi_conn *conn)
|
||||
{
|
||||
TRACE_ENTRY();
|
||||
|
||||
@@ -477,6 +477,7 @@ static int iscsi_conn_alloc(struct iscsi_session *session,
|
||||
INIT_LIST_HEAD(&conn->write_list);
|
||||
INIT_LIST_HEAD(&conn->written_list);
|
||||
setup_timer(&conn->rsp_timer, conn_rsp_timer_fn, (unsigned long)conn);
|
||||
init_waitqueue_head(&conn->read_state_waitQ);
|
||||
init_completion(&conn->ready_to_free);
|
||||
INIT_LIST_HEAD(&conn->reinst_pending_cmd_list);
|
||||
|
||||
|
||||
@@ -200,7 +200,6 @@ struct iscsi_cmnd *cmnd_alloc(struct iscsi_conn *conn,
|
||||
cmnd->scst_state = ISCSI_CMD_STATE_NEW;
|
||||
cmnd->conn = conn;
|
||||
cmnd->parent_req = parent;
|
||||
init_waitqueue_head(&cmnd->scst_waitQ);
|
||||
|
||||
if (parent == NULL) {
|
||||
conn_get(conn);
|
||||
@@ -1368,6 +1367,108 @@ static inline u32 get_next_ttt(struct iscsi_conn *conn)
|
||||
return cpu_to_be32(ttt);
|
||||
}
|
||||
|
||||
int cmnd_rx_continue(struct iscsi_cmnd *req)
|
||||
{
|
||||
struct iscsi_conn *conn = req->conn;
|
||||
struct iscsi_session *session = conn->session;
|
||||
struct iscsi_scsi_cmd_hdr *req_hdr = cmnd_hdr(req);
|
||||
struct scst_cmd *scst_cmd = req->scst_cmd;
|
||||
scst_data_direction dir;
|
||||
int res = 0;
|
||||
|
||||
TRACE_ENTRY();
|
||||
|
||||
TRACE_DBG("scsi command: %02x", req_hdr->scb[0]);
|
||||
|
||||
if (unlikely(req->scst_state != ISCSI_CMD_STATE_AFTER_PREPROC)) {
|
||||
TRACE_DBG("req %p is in %x state", req, req->scst_state);
|
||||
if (req->scst_state == ISCSI_CMD_STATE_PROCESSED) {
|
||||
cmnd_reject_scsi_cmd(req);
|
||||
goto out;
|
||||
}
|
||||
if (unlikely(req->tm_aborted)) {
|
||||
TRACE_MGMT_DBG("req %p (scst_cmd %p) aborted", req,
|
||||
req->scst_cmd);
|
||||
cmnd_prepare_get_rejected_cmd_data(req);
|
||||
goto out;
|
||||
}
|
||||
sBUG();
|
||||
}
|
||||
|
||||
dir = scst_cmd_get_data_direction(scst_cmd);
|
||||
if (dir & SCST_DATA_WRITE) {
|
||||
req->is_unsolicited_data = !(req_hdr->flags & ISCSI_CMD_FINAL);
|
||||
req->r2t_length = be32_to_cpu(req_hdr->data_length) -
|
||||
req->pdu.datasize;
|
||||
if (req->r2t_length > 0)
|
||||
req->data_waiting = 1;
|
||||
} else {
|
||||
if (unlikely(!(req_hdr->flags & ISCSI_CMD_FINAL) ||
|
||||
req->pdu.datasize)) {
|
||||
PRINT_ERROR("Unexpected unsolicited data (ITT %x "
|
||||
"CDB %x", cmnd_itt(req), req_hdr->scb[0]);
|
||||
scst_set_cmd_error(scst_cmd,
|
||||
SCST_LOAD_SENSE(iscsi_sense_unexpected_unsolicited_data));
|
||||
if (scst_cmd_get_sense_buffer(scst_cmd) != NULL)
|
||||
create_status_rsp(req, SAM_STAT_CHECK_CONDITION,
|
||||
scst_cmd_get_sense_buffer(scst_cmd),
|
||||
scst_cmd_get_sense_buffer_len(scst_cmd));
|
||||
else
|
||||
create_status_rsp(req, SAM_STAT_BUSY, NULL, 0);
|
||||
cmnd_reject_scsi_cmd(req);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
req->target_task_tag = get_next_ttt(conn);
|
||||
if (dir != SCST_DATA_BIDI) {
|
||||
req->sg = scst_cmd_get_sg(scst_cmd);
|
||||
req->sg_cnt = scst_cmd_get_sg_cnt(scst_cmd);
|
||||
req->bufflen = scst_cmd_get_bufflen(scst_cmd);
|
||||
} else {
|
||||
req->sg = scst_cmd_get_in_sg(scst_cmd);
|
||||
req->sg_cnt = scst_cmd_get_in_sg_cnt(scst_cmd);
|
||||
req->bufflen = scst_cmd_get_in_bufflen(scst_cmd);
|
||||
}
|
||||
if (unlikely(req->r2t_length > req->bufflen)) {
|
||||
PRINT_ERROR("req->r2t_length %d > req->bufflen %d",
|
||||
req->r2t_length, req->bufflen);
|
||||
req->r2t_length = req->bufflen;
|
||||
}
|
||||
|
||||
TRACE_DBG("req=%p, dir=%d, is_unsolicited_data=%d, "
|
||||
"r2t_length=%d, bufflen=%d", req, dir,
|
||||
req->is_unsolicited_data, req->r2t_length, req->bufflen);
|
||||
|
||||
if (unlikely(!session->sess_param.immediate_data &&
|
||||
req->pdu.datasize)) {
|
||||
PRINT_ERROR("Initiator %s violated negotiated paremeters: "
|
||||
"forbidden immediate data sent (ITT %x, op %x)",
|
||||
session->initiator_name, cmnd_itt(req),
|
||||
req_hdr->scb[0]);
|
||||
res = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (unlikely(session->sess_param.initial_r2t &&
|
||||
!(req_hdr->flags & ISCSI_CMD_FINAL))) {
|
||||
PRINT_ERROR("Initiator %s violated negotiated paremeters: "
|
||||
"initial R2T is required (ITT %x, op %x)",
|
||||
session->initiator_name, cmnd_itt(req),
|
||||
req_hdr->scb[0]);
|
||||
res = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (req->pdu.datasize)
|
||||
res = cmnd_prepare_recv_pdu(conn, req, 0, req->pdu.datasize);
|
||||
|
||||
out:
|
||||
/* Aborted commands will be freed in cmnd_rx_end() */
|
||||
TRACE_EXIT_RES(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int scsi_cmnd_start(struct iscsi_cmnd *req)
|
||||
{
|
||||
struct iscsi_conn *conn = req->conn;
|
||||
@@ -1492,92 +1593,17 @@ static int scsi_cmnd_start(struct iscsi_cmnd *req)
|
||||
TRACE_DBG("START Command (tag %d, queue_type %d)",
|
||||
req_hdr->itt, scst_cmd->queue_type);
|
||||
req->scst_state = ISCSI_CMD_STATE_RX_CMD;
|
||||
conn->rx_task = current;
|
||||
scst_cmd_init_stage1_done(scst_cmd, SCST_CONTEXT_DIRECT, 0);
|
||||
|
||||
wait_event(req->scst_waitQ, req->scst_state != ISCSI_CMD_STATE_RX_CMD);
|
||||
|
||||
if (unlikely(req->scst_state != ISCSI_CMD_STATE_AFTER_PREPROC)) {
|
||||
TRACE_DBG("req %p is in %x state", req, req->scst_state);
|
||||
if (req->scst_state == ISCSI_CMD_STATE_PROCESSED) {
|
||||
cmnd_reject_scsi_cmd(req);
|
||||
goto out;
|
||||
}
|
||||
if (unlikely(req->tm_aborted)) {
|
||||
TRACE_MGMT_DBG("req %p (scst_cmd %p) aborted", req,
|
||||
req->scst_cmd);
|
||||
cmnd_prepare_get_rejected_cmd_data(req);
|
||||
goto out;
|
||||
}
|
||||
sBUG();
|
||||
if (req->scst_state != ISCSI_CMD_STATE_RX_CMD)
|
||||
res = cmnd_rx_continue(req);
|
||||
else {
|
||||
TRACE_DBG("Delaying req %p post processing (scst_state %d)",
|
||||
req, req->scst_state);
|
||||
res = 1;
|
||||
}
|
||||
|
||||
dir = scst_cmd_get_data_direction(scst_cmd);
|
||||
if (dir & SCST_DATA_WRITE) {
|
||||
req->is_unsolicited_data = !(req_hdr->flags & ISCSI_CMD_FINAL);
|
||||
req->r2t_length = be32_to_cpu(req_hdr->data_length) -
|
||||
req->pdu.datasize;
|
||||
if (req->r2t_length > 0)
|
||||
req->data_waiting = 1;
|
||||
} else {
|
||||
if (unlikely(!(req_hdr->flags & ISCSI_CMD_FINAL) ||
|
||||
req->pdu.datasize)) {
|
||||
PRINT_ERROR("Unexpected unsolicited data (ITT %x "
|
||||
"CDB %x", cmnd_itt(req), req_hdr->scb[0]);
|
||||
scst_set_cmd_error(scst_cmd,
|
||||
SCST_LOAD_SENSE(iscsi_sense_unexpected_unsolicited_data));
|
||||
if (scst_cmd_get_sense_buffer(scst_cmd) != NULL)
|
||||
create_status_rsp(req, SAM_STAT_CHECK_CONDITION,
|
||||
scst_cmd_get_sense_buffer(scst_cmd),
|
||||
scst_cmd_get_sense_buffer_len(scst_cmd));
|
||||
else
|
||||
create_status_rsp(req, SAM_STAT_BUSY, NULL, 0);
|
||||
cmnd_reject_scsi_cmd(req);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
req->target_task_tag = get_next_ttt(conn);
|
||||
if (dir != SCST_DATA_BIDI) {
|
||||
req->sg = scst_cmd_get_sg(scst_cmd);
|
||||
req->sg_cnt = scst_cmd_get_sg_cnt(scst_cmd);
|
||||
req->bufflen = scst_cmd_get_bufflen(scst_cmd);
|
||||
} else {
|
||||
req->sg = scst_cmd_get_in_sg(scst_cmd);
|
||||
req->sg_cnt = scst_cmd_get_in_sg_cnt(scst_cmd);
|
||||
req->bufflen = scst_cmd_get_in_bufflen(scst_cmd);
|
||||
}
|
||||
if (unlikely(req->r2t_length > req->bufflen)) {
|
||||
PRINT_ERROR("req->r2t_length %d > req->bufflen %d",
|
||||
req->r2t_length, req->bufflen);
|
||||
req->r2t_length = req->bufflen;
|
||||
}
|
||||
|
||||
TRACE_DBG("req=%p, dir=%d, is_unsolicited_data=%d, "
|
||||
"r2t_length=%d, bufflen=%d", req, dir,
|
||||
req->is_unsolicited_data, req->r2t_length, req->bufflen);
|
||||
|
||||
if (unlikely(!session->sess_param.immediate_data &&
|
||||
req->pdu.datasize)) {
|
||||
PRINT_ERROR("Initiator %s violated negotiated paremeters: "
|
||||
"forbidden immediate data sent (ITT %x, op %x)",
|
||||
session->initiator_name, cmnd_itt(req),
|
||||
req_hdr->scb[0]);
|
||||
res = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (unlikely(session->sess_param.initial_r2t &&
|
||||
!(req_hdr->flags & ISCSI_CMD_FINAL))) {
|
||||
PRINT_ERROR("Initiator %s violated negotiated paremeters: "
|
||||
"initial R2T is required (ITT %x, op %x)",
|
||||
session->initiator_name, cmnd_itt(req),
|
||||
req_hdr->scb[0]);
|
||||
res = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (req->pdu.datasize)
|
||||
res = cmnd_prepare_recv_pdu(conn, req, 0, req->pdu.datasize);
|
||||
out:
|
||||
/* Aborted commands will be freed in cmnd_rx_end() */
|
||||
TRACE_EXIT_RES(res);
|
||||
@@ -2619,18 +2645,26 @@ static int iscsi_alloc_data_buf(struct scst_cmd *cmd)
|
||||
static inline void iscsi_set_state_wake_up(struct iscsi_cmnd *req,
|
||||
int new_state)
|
||||
{
|
||||
/*
|
||||
* We use wait_event() to wait for the state change, but it checks its
|
||||
* condition without any protection, so without cmnd_get() it is
|
||||
* possible that req will die "immediately" after the state assignment
|
||||
* and wake_up() will operate on dead data. We use the ordered version
|
||||
* of cmnd_get(), because "get" must be done before the state
|
||||
* assignment.
|
||||
*/
|
||||
cmnd_get_ordered(req);
|
||||
req->scst_state = new_state;
|
||||
wake_up(&req->scst_waitQ);
|
||||
cmnd_put(req);
|
||||
if (req->conn->rx_task == current)
|
||||
req->scst_state = new_state;
|
||||
else {
|
||||
/*
|
||||
* We wait for the state change without any protection, so
|
||||
* without cmnd_get() it is possible that req will die
|
||||
* "immediately" after the state assignment and
|
||||
* iscsi_make_conn_rd_active() will operate on dead data.
|
||||
* We use the ordered version of cmnd_get(), because "get"
|
||||
* must be done before the state assignment.
|
||||
*/
|
||||
cmnd_get_ordered(req);
|
||||
req->scst_state = new_state;
|
||||
iscsi_make_conn_rd_active(req->conn);
|
||||
if (unlikely(req->conn->closing)) {
|
||||
TRACE_DBG("Waiking up closing conn %p", req->conn);
|
||||
wake_up(&req->conn->read_state_waitQ);
|
||||
}
|
||||
cmnd_put(req);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -223,6 +223,7 @@ struct iscsi_conn {
|
||||
u32 read_size;
|
||||
int read_state;
|
||||
struct iovec *read_iov;
|
||||
struct task_struct *rx_task;
|
||||
uint32_t rpadding;
|
||||
|
||||
struct iscsi_target *target;
|
||||
@@ -233,6 +234,7 @@ struct iscsi_conn {
|
||||
struct iscsi_conn *conn_reinst_successor;
|
||||
struct list_head reinst_pending_cmd_list;
|
||||
|
||||
wait_queue_head_t read_state_waitQ;
|
||||
struct completion ready_to_free;
|
||||
|
||||
/* Doesn't need any protection */
|
||||
@@ -355,7 +357,6 @@ struct iscsi_cmnd {
|
||||
struct list_head rx_ddigest_cmd_list;
|
||||
struct list_head rx_ddigest_cmd_list_entry;
|
||||
|
||||
wait_queue_head_t scst_waitQ;
|
||||
int scst_state;
|
||||
union {
|
||||
struct scst_cmd *scst_cmd;
|
||||
@@ -414,6 +415,7 @@ extern wait_queue_head_t iscsi_wr_waitQ;
|
||||
extern struct iscsi_cmnd *cmnd_alloc(struct iscsi_conn *,
|
||||
struct iscsi_cmnd *parent);
|
||||
extern int cmnd_rx_start(struct iscsi_cmnd *);
|
||||
extern int cmnd_rx_continue(struct iscsi_cmnd *req);
|
||||
extern void cmnd_rx_end(struct iscsi_cmnd *);
|
||||
extern void cmnd_tx_start(struct iscsi_cmnd *);
|
||||
extern void cmnd_tx_end(struct iscsi_cmnd *);
|
||||
@@ -429,6 +431,7 @@ extern void conn_reinst_finished(struct iscsi_conn *);
|
||||
extern int conn_add(struct iscsi_session *, struct iscsi_kern_conn_info *);
|
||||
extern int conn_del(struct iscsi_session *, struct iscsi_kern_conn_info *);
|
||||
extern int conn_free(struct iscsi_conn *);
|
||||
extern void iscsi_make_conn_rd_active(struct iscsi_conn *conn);
|
||||
|
||||
#define ISCSI_CONN_ACTIVE_CLOSE 1
|
||||
#define ISCSI_CONN_DELETING 2
|
||||
|
||||
@@ -26,31 +26,23 @@
|
||||
#include "digest.h"
|
||||
|
||||
enum rx_state {
|
||||
RX_INIT_BHS, /* Must be zero. */
|
||||
RX_INIT_BHS, /* Must be zero for better "switch" optimiztion. */
|
||||
RX_BHS,
|
||||
|
||||
RX_INIT_AHS,
|
||||
RX_AHS,
|
||||
|
||||
RX_INIT_HDIGEST,
|
||||
RX_HDIGEST,
|
||||
RX_CHECK_HDIGEST,
|
||||
|
||||
RX_INIT_DATA,
|
||||
RX_CMD_START,
|
||||
RX_DATA,
|
||||
|
||||
RX_INIT_PADDING,
|
||||
RX_PADDING,
|
||||
|
||||
RX_INIT_DDIGEST,
|
||||
RX_DDIGEST,
|
||||
RX_CHECK_DDIGEST,
|
||||
|
||||
RX_END,
|
||||
|
||||
RX_CMD_CONTINUE,
|
||||
RX_INIT_HDIGEST,
|
||||
RX_CHECK_HDIGEST,
|
||||
RX_INIT_DDIGEST,
|
||||
RX_CHECK_DDIGEST,
|
||||
RX_AHS,
|
||||
RX_PADDING,
|
||||
};
|
||||
|
||||
enum tx_state {
|
||||
TX_INIT, /* Must be zero. */
|
||||
TX_INIT = 0, /* Must be zero for better "switch" optimiztion. */
|
||||
TX_BHS_DATA,
|
||||
TX_INIT_PADDING,
|
||||
TX_PADDING,
|
||||
@@ -439,6 +431,14 @@ static void close_conn(struct iscsi_conn *conn)
|
||||
|
||||
if (conn->read_state != RX_INIT_BHS) {
|
||||
struct iscsi_cmnd *cmnd = conn->read_cmnd;
|
||||
|
||||
if (cmnd->scst_state == ISCSI_CMD_STATE_RX_CMD) {
|
||||
TRACE_DBG("Going to wait for cmnd %p to change state "
|
||||
"from RX_CMD", cmnd);
|
||||
}
|
||||
wait_event(conn->read_state_waitQ,
|
||||
cmnd->scst_state != ISCSI_CMD_STATE_RX_CMD);
|
||||
|
||||
conn->read_cmnd = NULL;
|
||||
conn->read_state = RX_INIT_BHS;
|
||||
req_cmnd_release_force(cmnd, 0);
|
||||
@@ -615,7 +615,7 @@ static inline void iscsi_conn_init_read(struct iscsi_conn *conn,
|
||||
return;
|
||||
}
|
||||
|
||||
static void iscsi_conn_read_ahs(struct iscsi_conn *conn,
|
||||
static void iscsi_conn_prepare_read_ahs(struct iscsi_conn *conn,
|
||||
struct iscsi_cmnd *cmnd)
|
||||
{
|
||||
int asize = (cmnd->pdu.ahssize + 3) & -4;
|
||||
@@ -643,187 +643,87 @@ static struct iscsi_cmnd *iscsi_get_send_cmnd(struct iscsi_conn *conn)
|
||||
return cmnd;
|
||||
}
|
||||
|
||||
static int do_recv(struct iscsi_conn *conn, int state)
|
||||
/* Returns number of bytes left to receive or <0 for error */
|
||||
static int do_recv(struct iscsi_conn *conn)
|
||||
{
|
||||
mm_segment_t oldfs;
|
||||
struct msghdr msg;
|
||||
int res, first_len;
|
||||
int res;
|
||||
|
||||
sBUG_ON(conn->read_cmnd == NULL);
|
||||
EXTRACHECKS_BUG_ON(conn->read_cmnd == NULL);
|
||||
|
||||
if (unlikely(conn->closing)) {
|
||||
res = -EIO;
|
||||
goto out;
|
||||
}
|
||||
do {
|
||||
mm_segment_t oldfs;
|
||||
struct msghdr msg;
|
||||
int first_len;
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.msg_iov = conn->read_msg.msg_iov;
|
||||
msg.msg_iovlen = conn->read_msg.msg_iovlen;
|
||||
first_len = msg.msg_iov->iov_len;
|
||||
if (unlikely(conn->closing)) {
|
||||
res = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
oldfs = get_fs();
|
||||
set_fs(get_ds());
|
||||
res = sock_recvmsg(conn->sock, &msg, conn->read_size,
|
||||
MSG_DONTWAIT | MSG_NOSIGNAL);
|
||||
set_fs(oldfs);
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.msg_iov = conn->read_msg.msg_iov;
|
||||
msg.msg_iovlen = conn->read_msg.msg_iovlen;
|
||||
first_len = msg.msg_iov->iov_len;
|
||||
|
||||
if (res <= 0) {
|
||||
switch (res) {
|
||||
case -EAGAIN:
|
||||
case -ERESTARTSYS:
|
||||
TRACE_DBG("EAGAIN or ERESTARTSYS (%d) received for "
|
||||
"conn %p", res, conn);
|
||||
break;
|
||||
default:
|
||||
PRINT_ERROR("sock_recvmsg() failed: %d", res);
|
||||
mark_conn_closed(conn);
|
||||
oldfs = get_fs();
|
||||
set_fs(get_ds());
|
||||
res = sock_recvmsg(conn->sock, &msg, conn->read_size,
|
||||
MSG_DONTWAIT | MSG_NOSIGNAL);
|
||||
set_fs(oldfs);
|
||||
|
||||
if (res > 0) {
|
||||
/*
|
||||
* To save some considerable effort and CPU power we
|
||||
* suppose that TCP functions adjust
|
||||
* conn->read_msg.msg_iov and conn->read_msg.msg_iovlen
|
||||
* on amount of copied data. This BUG_ON is intended
|
||||
* to catch if it is changed in the future.
|
||||
*/
|
||||
sBUG_ON((res >= first_len) &&
|
||||
(conn->read_msg.msg_iov->iov_len != 0));
|
||||
conn->read_size -= res;
|
||||
if (conn->read_size != 0) {
|
||||
if (res >= first_len) {
|
||||
int done = 1 + ((res - first_len) >> PAGE_SHIFT);
|
||||
conn->read_msg.msg_iov += done;
|
||||
conn->read_msg.msg_iovlen -= done;
|
||||
}
|
||||
}
|
||||
res = conn->read_size;
|
||||
} else {
|
||||
switch (res) {
|
||||
case -EAGAIN:
|
||||
TRACE_DBG("EAGAIN received for conn %p", conn);
|
||||
res = conn->read_size;
|
||||
break;
|
||||
case -ERESTARTSYS:
|
||||
TRACE_DBG("ERESTARTSYS received for conn %p", conn);
|
||||
continue;
|
||||
default:
|
||||
PRINT_ERROR("sock_recvmsg() failed: %d", res);
|
||||
mark_conn_closed(conn);
|
||||
if (res == 0)
|
||||
res = -EIO;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* To save some considerable effort and CPU power we suppose
|
||||
* that TCP functions adjust conn->read_msg.msg_iov and
|
||||
* conn->read_msg.msg_iovlen on amount of copied data. This
|
||||
* BUG_ON is intended to catch if it is changed in the future.
|
||||
*/
|
||||
sBUG_ON((res >= first_len) &&
|
||||
(conn->read_msg.msg_iov->iov_len != 0));
|
||||
conn->read_size -= res;
|
||||
if (conn->read_size) {
|
||||
if (res >= first_len) {
|
||||
int done =
|
||||
1 + ((res - first_len) >> PAGE_SHIFT);
|
||||
conn->read_msg.msg_iov += done;
|
||||
conn->read_msg.msg_iovlen -= done;
|
||||
}
|
||||
} else
|
||||
conn->read_state = state;
|
||||
}
|
||||
} while (res > 0);
|
||||
|
||||
out:
|
||||
TRACE_EXIT_RES(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int rx_hdigest(struct iscsi_conn *conn)
|
||||
static int iscsi_rx_check_ddigest(struct iscsi_conn *conn)
|
||||
{
|
||||
struct iscsi_cmnd *cmnd = conn->read_cmnd;
|
||||
int res = digest_rx_header(cmnd);
|
||||
int res;
|
||||
|
||||
if (unlikely(res != 0)) {
|
||||
PRINT_ERROR("rx header digest for initiator %s failed "
|
||||
"(%d)", conn->session->initiator_name, res);
|
||||
mark_conn_closed(conn);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static struct iscsi_cmnd *create_cmnd(struct iscsi_conn *conn)
|
||||
{
|
||||
struct iscsi_cmnd *cmnd;
|
||||
|
||||
cmnd = cmnd_alloc(conn, NULL);
|
||||
iscsi_conn_init_read(cmnd->conn, (void __force __user *)&cmnd->pdu.bhs,
|
||||
sizeof(cmnd->pdu.bhs));
|
||||
conn->read_state = RX_BHS;
|
||||
|
||||
return cmnd;
|
||||
}
|
||||
|
||||
/* Returns >0 for success, <=0 for error or successful finish */
|
||||
static int recv(struct iscsi_conn *conn)
|
||||
{
|
||||
struct iscsi_cmnd *cmnd = conn->read_cmnd;
|
||||
int hdigest, ddigest, res = 1, rc;
|
||||
|
||||
TRACE_ENTRY();
|
||||
|
||||
hdigest = conn->hdigest_type & DIGEST_NONE ? 0 : 1;
|
||||
ddigest = conn->ddigest_type & DIGEST_NONE ? 0 : 1;
|
||||
|
||||
switch (conn->read_state) {
|
||||
case RX_INIT_BHS:
|
||||
sBUG_ON(cmnd != NULL);
|
||||
cmnd = conn->read_cmnd = create_cmnd(conn);
|
||||
case RX_BHS:
|
||||
res = do_recv(conn, RX_INIT_AHS);
|
||||
if (res <= 0 || conn->read_state != RX_INIT_AHS)
|
||||
break;
|
||||
case RX_INIT_AHS:
|
||||
iscsi_cmnd_get_length(&cmnd->pdu);
|
||||
if (cmnd->pdu.ahssize) {
|
||||
iscsi_conn_read_ahs(conn, cmnd);
|
||||
conn->read_state = RX_AHS;
|
||||
} else
|
||||
conn->read_state =
|
||||
hdigest ? RX_INIT_HDIGEST : RX_INIT_DATA;
|
||||
|
||||
if (conn->read_state != RX_AHS)
|
||||
break;
|
||||
case RX_AHS:
|
||||
res = do_recv(conn, hdigest ? RX_INIT_HDIGEST : RX_INIT_DATA);
|
||||
if (res <= 0 || conn->read_state != RX_INIT_HDIGEST)
|
||||
break;
|
||||
case RX_INIT_HDIGEST:
|
||||
iscsi_conn_init_read(conn,
|
||||
(void __force __user *)&cmnd->hdigest, sizeof(u32));
|
||||
conn->read_state = RX_HDIGEST;
|
||||
case RX_HDIGEST:
|
||||
res = do_recv(conn, RX_CHECK_HDIGEST);
|
||||
if (res <= 0 || conn->read_state != RX_CHECK_HDIGEST)
|
||||
break;
|
||||
case RX_CHECK_HDIGEST:
|
||||
rc = rx_hdigest(conn);
|
||||
if (likely(rc == 0))
|
||||
conn->read_state = RX_INIT_DATA;
|
||||
else {
|
||||
res = rc;
|
||||
break;
|
||||
}
|
||||
case RX_INIT_DATA:
|
||||
rc = cmnd_rx_start(cmnd);
|
||||
if (unlikely(rc != 0)) {
|
||||
sBUG_ON(!conn->closing);
|
||||
conn->read_state = RX_END;
|
||||
res = rc;
|
||||
/* cmnd will be freed in close_conn() */
|
||||
goto out;
|
||||
}
|
||||
conn->read_state = cmnd->pdu.datasize ? RX_DATA : RX_END;
|
||||
if (conn->read_state != RX_DATA)
|
||||
break;
|
||||
case RX_DATA:
|
||||
res = do_recv(conn, RX_INIT_PADDING);
|
||||
if (res <= 0 || conn->read_state != RX_INIT_PADDING)
|
||||
break;
|
||||
case RX_INIT_PADDING:
|
||||
{
|
||||
int psz = ((cmnd->pdu.datasize + 3) & -4) - cmnd->pdu.datasize;
|
||||
if (psz != 0) {
|
||||
TRACE_DBG("padding %d bytes", psz);
|
||||
iscsi_conn_init_read(conn,
|
||||
(void __force __user *)&conn->rpadding, psz);
|
||||
conn->read_state = RX_PADDING;
|
||||
} else if (ddigest)
|
||||
conn->read_state = RX_INIT_DDIGEST;
|
||||
else
|
||||
conn->read_state = RX_END;
|
||||
break;
|
||||
}
|
||||
case RX_PADDING:
|
||||
res = do_recv(conn, ddigest ? RX_INIT_DDIGEST : RX_END);
|
||||
if (res <= 0 || conn->read_state != RX_INIT_DDIGEST)
|
||||
break;
|
||||
case RX_INIT_DDIGEST:
|
||||
iscsi_conn_init_read(conn,
|
||||
(void __force __user *)&cmnd->ddigest, sizeof(u32));
|
||||
conn->read_state = RX_DDIGEST;
|
||||
case RX_DDIGEST:
|
||||
res = do_recv(conn, RX_CHECK_DDIGEST);
|
||||
if (res <= 0 || conn->read_state != RX_CHECK_DDIGEST)
|
||||
break;
|
||||
case RX_CHECK_DDIGEST:
|
||||
res = do_recv(conn);
|
||||
if (res == 0) {
|
||||
conn->read_state = RX_END;
|
||||
|
||||
if (cmnd->pdu.datasize <= 16*1024) {
|
||||
/*
|
||||
* It's cache hot, so let's compute it inline. The
|
||||
@@ -833,8 +733,8 @@ static int recv(struct iscsi_conn *conn)
|
||||
TRACE_DBG("cmnd %p, opcode %x: checking RX "
|
||||
"ddigest inline", cmnd, cmnd_opcode(cmnd));
|
||||
cmnd->ddigest_checked = 1;
|
||||
rc = digest_rx_data(cmnd);
|
||||
if (unlikely(rc != 0)) {
|
||||
res = digest_rx_data(cmnd);
|
||||
if (unlikely(res != 0)) {
|
||||
mark_conn_closed(conn);
|
||||
goto out;
|
||||
}
|
||||
@@ -849,60 +749,182 @@ static int recv(struct iscsi_conn *conn)
|
||||
*/
|
||||
TRACE_DBG("cmnd %p, opcode %x: checking NOP RX "
|
||||
"ddigest", cmnd, cmnd_opcode(cmnd));
|
||||
rc = digest_rx_data(cmnd);
|
||||
if (unlikely(rc != 0)) {
|
||||
res = digest_rx_data(cmnd);
|
||||
if (unlikely(res != 0)) {
|
||||
mark_conn_closed(conn);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
PRINT_CRIT_ERROR("%d %x", conn->read_state, cmnd_opcode(cmnd));
|
||||
sBUG();
|
||||
}
|
||||
|
||||
if (res <= 0)
|
||||
goto out;
|
||||
|
||||
if (conn->read_state != RX_END)
|
||||
goto out;
|
||||
|
||||
if (unlikely(conn->read_size)) {
|
||||
PRINT_CRIT_ERROR("%d %x %d", res, cmnd_opcode(cmnd),
|
||||
conn->read_size);
|
||||
sBUG();
|
||||
}
|
||||
|
||||
conn->read_cmnd = NULL;
|
||||
conn->read_state = RX_INIT_BHS;
|
||||
|
||||
cmnd_rx_end(cmnd);
|
||||
|
||||
sBUG_ON(conn->read_size != 0);
|
||||
|
||||
res = 0;
|
||||
}
|
||||
|
||||
out:
|
||||
TRACE_EXIT_RES(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* No locks, conn is rd processing */
|
||||
static int process_read_io(struct iscsi_conn *conn, int *closed)
|
||||
static void process_read_io(struct iscsi_conn *conn, int *closed)
|
||||
{
|
||||
struct iscsi_cmnd *cmnd = conn->read_cmnd;
|
||||
int res;
|
||||
|
||||
do {
|
||||
res = recv(conn);
|
||||
if (unlikely(conn->closing)) {
|
||||
start_close_conn(conn);
|
||||
*closed = 1;
|
||||
break;
|
||||
}
|
||||
} while (res > 0);
|
||||
TRACE_ENTRY();
|
||||
|
||||
TRACE_EXIT_RES(res);
|
||||
return res;
|
||||
/* In case of error cmnd will be freed in close_conn() */
|
||||
|
||||
do {
|
||||
switch (conn->read_state) {
|
||||
case RX_INIT_BHS:
|
||||
EXTRACHECKS_BUG_ON(conn->read_cmnd != NULL);
|
||||
cmnd = cmnd_alloc(conn, NULL);
|
||||
conn->read_cmnd = cmnd;
|
||||
iscsi_conn_init_read(cmnd->conn,
|
||||
(void __force __user *)&cmnd->pdu.bhs,
|
||||
sizeof(cmnd->pdu.bhs));
|
||||
conn->read_state = RX_BHS;
|
||||
/* go through */
|
||||
|
||||
case RX_BHS:
|
||||
res = do_recv(conn);
|
||||
if (res == 0) {
|
||||
iscsi_cmnd_get_length(&cmnd->pdu);
|
||||
if (cmnd->pdu.ahssize == 0) {
|
||||
if ((conn->hdigest_type & DIGEST_NONE) == 0)
|
||||
conn->read_state = RX_INIT_HDIGEST;
|
||||
else
|
||||
conn->read_state = RX_CMD_START;
|
||||
} else {
|
||||
iscsi_conn_prepare_read_ahs(conn, cmnd);
|
||||
conn->read_state = RX_AHS;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case RX_CMD_START:
|
||||
res = cmnd_rx_start(cmnd);
|
||||
if (res == 0) {
|
||||
if (cmnd->pdu.datasize == 0)
|
||||
conn->read_state = RX_END;
|
||||
else
|
||||
conn->read_state = RX_DATA;
|
||||
} else if (res > 0)
|
||||
conn->read_state = RX_CMD_CONTINUE;
|
||||
else
|
||||
sBUG_ON(!conn->closing);
|
||||
break;
|
||||
|
||||
case RX_CMD_CONTINUE:
|
||||
if (cmnd->scst_state == ISCSI_CMD_STATE_RX_CMD) {
|
||||
TRACE_DBG("cmnd %p is still in RX_CMD state",
|
||||
cmnd);
|
||||
res = 1;
|
||||
break;
|
||||
}
|
||||
res = cmnd_rx_continue(cmnd);
|
||||
if (unlikely(res != 0))
|
||||
sBUG_ON(!conn->closing);
|
||||
else {
|
||||
if (cmnd->pdu.datasize == 0)
|
||||
conn->read_state = RX_END;
|
||||
else
|
||||
conn->read_state = RX_DATA;
|
||||
}
|
||||
break;
|
||||
|
||||
case RX_DATA:
|
||||
res = do_recv(conn);
|
||||
if (res == 0) {
|
||||
int psz = ((cmnd->pdu.datasize + 3) & -4) - cmnd->pdu.datasize;
|
||||
if (psz != 0) {
|
||||
TRACE_DBG("padding %d bytes", psz);
|
||||
iscsi_conn_init_read(conn,
|
||||
(void __force __user *)&conn->rpadding, psz);
|
||||
conn->read_state = RX_PADDING;
|
||||
} else if ((conn->ddigest_type & DIGEST_NONE) != 0)
|
||||
conn->read_state = RX_END;
|
||||
else
|
||||
conn->read_state = RX_INIT_DDIGEST;
|
||||
}
|
||||
break;
|
||||
|
||||
case RX_END:
|
||||
if (unlikely(conn->read_size != 0)) {
|
||||
PRINT_CRIT_ERROR("%d %x %d", res,
|
||||
cmnd_opcode(cmnd), conn->read_size);
|
||||
sBUG();
|
||||
}
|
||||
conn->read_cmnd = NULL;
|
||||
conn->read_state = RX_INIT_BHS;
|
||||
|
||||
cmnd_rx_end(cmnd);
|
||||
|
||||
EXTRACHECKS_BUG_ON(conn->read_size != 0);
|
||||
break;
|
||||
|
||||
case RX_INIT_HDIGEST:
|
||||
iscsi_conn_init_read(conn,
|
||||
(void __force __user *)&cmnd->hdigest, sizeof(u32));
|
||||
conn->read_state = RX_CHECK_HDIGEST;
|
||||
/* go through */
|
||||
|
||||
case RX_CHECK_HDIGEST:
|
||||
res = do_recv(conn);
|
||||
if (res == 0) {
|
||||
res = digest_rx_header(cmnd);
|
||||
if (unlikely(res != 0)) {
|
||||
PRINT_ERROR("rx header digest for "
|
||||
"initiator %s failed (%d)",
|
||||
conn->session->initiator_name,
|
||||
res);
|
||||
mark_conn_closed(conn);
|
||||
} else
|
||||
conn->read_state = RX_CMD_START;
|
||||
}
|
||||
break;
|
||||
|
||||
case RX_INIT_DDIGEST:
|
||||
iscsi_conn_init_read(conn,
|
||||
(void __force __user *)&cmnd->ddigest,
|
||||
sizeof(u32));
|
||||
conn->read_state = RX_CHECK_DDIGEST;
|
||||
/* go through */
|
||||
|
||||
case RX_CHECK_DDIGEST:
|
||||
res = iscsi_rx_check_ddigest(conn);
|
||||
break;
|
||||
|
||||
case RX_AHS:
|
||||
res = do_recv(conn);
|
||||
if (res == 0) {
|
||||
if ((conn->hdigest_type & DIGEST_NONE) == 0)
|
||||
conn->read_state = RX_INIT_HDIGEST;
|
||||
else
|
||||
conn->read_state = RX_CMD_START;
|
||||
}
|
||||
break;
|
||||
|
||||
case RX_PADDING:
|
||||
res = do_recv(conn);
|
||||
if (res == 0) {
|
||||
if ((conn->ddigest_type & DIGEST_NONE) == 0)
|
||||
conn->read_state = RX_INIT_DDIGEST;
|
||||
else
|
||||
conn->read_state = RX_END;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
PRINT_CRIT_ERROR("%d %x", conn->read_state, cmnd_opcode(cmnd));
|
||||
sBUG();
|
||||
}
|
||||
} while (res == 0);
|
||||
|
||||
if (unlikely(conn->closing)) {
|
||||
start_close_conn(conn);
|
||||
*closed = 1;
|
||||
}
|
||||
|
||||
TRACE_EXIT();
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -920,7 +942,7 @@ static void scst_do_job_rd(void)
|
||||
*/
|
||||
|
||||
while (!list_empty(&iscsi_rd_list)) {
|
||||
int rc, closed = 0;
|
||||
int closed = 0;
|
||||
struct iscsi_conn *conn = list_entry(iscsi_rd_list.next,
|
||||
typeof(*conn), rd_list_entry);
|
||||
|
||||
@@ -934,7 +956,7 @@ static void scst_do_job_rd(void)
|
||||
#endif
|
||||
spin_unlock_bh(&iscsi_rd_lock);
|
||||
|
||||
rc = process_read_io(conn, &closed);
|
||||
process_read_io(conn, &closed);
|
||||
|
||||
spin_lock_bh(&iscsi_rd_lock);
|
||||
|
||||
@@ -944,7 +966,7 @@ static void scst_do_job_rd(void)
|
||||
#ifdef CONFIG_SCST_EXTRACHECKS
|
||||
conn->rd_task = NULL;
|
||||
#endif
|
||||
if ((rc == 0) || conn->rd_data_ready) {
|
||||
if (conn->rd_data_ready) {
|
||||
list_add_tail(&conn->rd_list_entry, &iscsi_rd_list);
|
||||
conn->rd_state = ISCSI_CONN_RD_STATE_IN_LIST;
|
||||
} else
|
||||
|
||||
@@ -220,6 +220,27 @@ out_err_unlock:
|
||||
goto out;
|
||||
}
|
||||
|
||||
static void __session_free(struct iscsi_session *session)
|
||||
{
|
||||
kfree(session->initiator_name);
|
||||
kfree(session);
|
||||
}
|
||||
|
||||
static void iscsi_unreg_sess_done(struct scst_session *scst_sess)
|
||||
{
|
||||
struct iscsi_session *session;
|
||||
|
||||
TRACE_ENTRY();
|
||||
|
||||
session = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess);
|
||||
|
||||
session->scst_sess = NULL;
|
||||
__session_free(session);
|
||||
|
||||
TRACE_EXIT();
|
||||
return;
|
||||
}
|
||||
|
||||
/* target_mutex supposed to be locked */
|
||||
int session_free(struct iscsi_session *session, bool del)
|
||||
{
|
||||
@@ -253,6 +274,9 @@ int session_free(struct iscsi_session *session, bool del)
|
||||
}
|
||||
}
|
||||
|
||||
if (del)
|
||||
list_del(&session->session_list_entry);
|
||||
|
||||
if (session->scst_sess != NULL) {
|
||||
/*
|
||||
* We must NOT call scst_unregister_session() in the waiting
|
||||
@@ -261,15 +285,10 @@ int session_free(struct iscsi_session *session, bool del)
|
||||
* and scst_mutex in SCST core (iscsi_report_aen() called by
|
||||
* SCST core under scst_mutex).
|
||||
*/
|
||||
scst_unregister_session(session->scst_sess, 0, NULL);
|
||||
session->scst_sess = NULL;
|
||||
}
|
||||
|
||||
if (del)
|
||||
list_del(&session->session_list_entry);
|
||||
|
||||
kfree(session->initiator_name);
|
||||
kfree(session);
|
||||
scst_unregister_session(session->scst_sess, 0,
|
||||
iscsi_unreg_sess_done);
|
||||
} else
|
||||
__session_free(session);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user