- 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:
Vladislav Bolkhovitin
2009-10-15 15:56:57 +00:00
parent c0cc23e7f8
commit cc83f517ea
5 changed files with 413 additions and 334 deletions

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}