- Fix for a possible DoS, when misbehavine scst_user's handler hangs several memory...
authorvlnb <vlnb@d57e44dd-8a1f-0410-8b47-8ef2f437770f>
Thu, 15 Oct 2009 15:56:57 +0000 (15:56 +0000)
committervlnb <vlnb@d57e44dd-8a1f-0410-8b47-8ef2f437770f>
Thu, 15 Oct 2009 15:56:57 +0000 (15:56 +0000)
 - 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: https://scst.svn.sourceforge.net/svnroot/scst/trunk@1224 d57e44dd-8a1f-0410-8b47-8ef2f437770f

iscsi-scst/kernel/conn.c
iscsi-scst/kernel/iscsi.c
iscsi-scst/kernel/iscsi.h
iscsi-scst/kernel/nthread.c
iscsi-scst/kernel/session.c

index d26cc50..21ec29c 100644 (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);
 
index cce47e5..ff6b17c 100644 (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();
-       }
-
-       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 (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;
        }
 
-       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;
 }
 
index 691a875..131cc8d 100644 (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
index c4d587a..31e2bfc 100644 (file)
 #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_CMD_START,
+       RX_DATA,
+       RX_END,
 
-       RX_INIT_AHS,
-       RX_AHS,
-
+       RX_CMD_CONTINUE,
        RX_INIT_HDIGEST,
-       RX_HDIGEST,
        RX_CHECK_HDIGEST,
-
-       RX_INIT_DATA,
-       RX_DATA,
-
-       RX_INIT_PADDING,
-       RX_PADDING,
-
        RX_INIT_DDIGEST,
-       RX_DDIGEST,
        RX_CHECK_DDIGEST,
-
-       RX_END,
+       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;
-
-       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) {
-               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);
-                       break;
+               if (unlikely(conn->closing)) {
+                       res = -EIO;
+                       goto out;
                }
-       } 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;
+
+               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;
+
+               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;
+                               }
                        }
-               } else
-                       conn->read_state = state;
-       }
+                       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;
+               }
+       } 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);
-
-       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;
+       int res;
 
-               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;
 
+       TRACE_ENTRY();
+
+       /* In case of error cmnd will be freed in close_conn() */
+
        do {
-               res = recv(conn);
-               if (unlikely(conn->closing)) {
-                       start_close_conn(conn);
-                       *closed = 1;
+               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);
+       } while (res == 0);
 
-       TRACE_EXIT_RES(res);
-       return res;
+       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
index 27cda57..35bcc21 100644 (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;
 }