Merge with IET r192-193:
authorvlnb <vlnb@d57e44dd-8a1f-0410-8b47-8ef2f437770f>
Fri, 30 Jan 2009 16:25:52 +0000 (16:25 +0000)
committervlnb <vlnb@d57e44dd-8a1f-0410-8b47-8ef2f437770f>
Fri, 30 Jan 2009 16:25:52 +0000 (16:25 +0000)
 - Introduce min*/max*() and more list_* macros

 - Multipart PDU support for discovery sessions

The conn's response buffer is now replaced with a list of buffers and
all users are updated accordingly. Discovery sessions can make use of
this mechanism, if the list of targets + addresses does not fit into a
single text PDU, the initiator may now retrieve subsequent parts.

This also fixes the data segment handling for discovery sessions.

Based on a patch originally provided by Shreyansh Jain <shrey.linux@gmail.com>

Signed-off-by: Arne Redlich <agr@powerkom-dd.de>
git-svn-id: https://scst.svn.sourceforge.net/svnroot/scst/trunk@650 d57e44dd-8a1f-0410-8b47-8ef2f437770f

iscsi-scst/usr/conn.c
iscsi-scst/usr/iscsi_hdr.h
iscsi-scst/usr/iscsid.c
iscsi-scst/usr/iscsid.h
iscsi-scst/usr/misc.h

index 8586304..b392d3c 100644 (file)
@@ -37,6 +37,7 @@ struct connection *conn_alloc(void)
        memset(conn, 0, sizeof(*conn));
        conn->state = STATE_FREE;
        param_set_defaults(conn->session_param, session_keys);
+       INIT_LIST_HEAD(&conn->rsp_buf_list);
 
        return conn;
 }
@@ -143,6 +144,19 @@ void conn_write_pdu(struct connection *conn)
        conn->rwsize = BHS_SIZE;
 }
 
+void conn_free_rsp_buf_list(struct connection *conn)
+{
+       struct buf_segment *seg, *tmp;
+
+       list_for_each_entry_safe(seg, tmp, &conn->rsp_buf_list, entry) {
+               list_del(&seg->entry);
+               free(seg);
+       }
+
+       conn->rsp.datasize = 0;
+       conn->rsp.data = NULL;
+}
+
 void conn_free_pdu(struct connection *conn)
 {
        conn->iostate = IOSTATE_FREE;
@@ -154,8 +168,5 @@ void conn_free_pdu(struct connection *conn)
                free(conn->rsp.ahs);
                conn->rsp.ahs = NULL;
        }
-       if (conn->rsp.data) {
-               free(conn->rsp.data);
-               conn->rsp.data = NULL;
-       }
+       conn_free_rsp_buf_list(conn);
 }
index 572c168..64b559d 100644 (file)
@@ -142,6 +142,7 @@ struct iscsi_login_rsp_hdr {
        u8  rsvd2[10];
 } __packed;
 
+#define ISCSI_FLG_CONTINUE             0x40
 #define ISCSI_FLG_FINAL                        0x80
 #define ISCSI_FLG_TRANSIT              0x80
 #define ISCSI_FLG_CSG_SECURITY         0x00
@@ -186,6 +187,8 @@ struct iscsi_login_rsp_hdr {
 #define ISCSI_STATUS_SVC_UNAVAILABLE   0x01
 #define ISCSI_STATUS_NO_RESOURCES      0x02
 
+#define ISCSI_RESERVED_TAG             0xffffffff
+
 struct iscsi_logout_req_hdr {
        u8  opcode;
        u8  flags;
index c52b98c..a600718 100644 (file)
 
 #include "iscsid.h"
 
+static u32 ttt;
+
+static u32 get_next_ttt(struct connection *conn __attribute__((unused)))
+{
+       ttt += 1;
+       return (ttt == ISCSI_RESERVED_TAG) ? ++ttt : ttt;
+}
+
 static struct iscsi_key login_keys[] = {
        {"InitiatorName",},
        {"InitiatorAlias",},
@@ -91,45 +99,90 @@ static char *next_key(char **data, int *datasize, char **value)
        return key;
 }
 
+static struct buf_segment *conn_alloc_buf_segment(struct connection *conn,
+                                                  size_t sz)
+{
+       struct buf_segment *seg = malloc(sizeof *seg + sz);
+
+       if (seg) {
+               seg->len = 0;
+               memset(seg->data, 0x0, sz);
+               list_add_tail(&seg->entry, &conn->rsp_buf_list);
+               log_debug(2, "alloc'ed new buf_segment\n");
+       }
+
+       return seg;
+}
+
 void text_key_add(struct connection *conn, char *key, char *value)
 {
+       struct buf_segment *seg;
        int keylen = strlen(key);
        int valuelen = strlen(value);
        int len = keylen + valuelen + 2;
-       char *buffer;
-
-       if (!conn->rsp.datasize) {
-               if (!conn->rsp_buffer) {
-                       conn->rsp_buffer = malloc(INCOMING_BUFSIZE);
-                       if (!conn->rsp_buffer) {
-                               log_error("Failed to alloc send buffer");
-                               return;
+       int off = 0;
+       int sz = 0;
+       int stage = 0;
+       size_t data_sz;
+
+       data_sz = (conn->state == STATE_FULL) ?
+               conn->session_param[key_max_xmit_data_length].exec_val :
+               INCOMING_BUFSIZE;
+
+       seg = list_empty(&conn->rsp_buf_list) ? NULL :
+               list_entry(conn->rsp_buf_list.q_back, struct buf_segment,
+                          entry);
+
+       while (len) {
+               if (!seg || seg->len == data_sz) {
+                       seg = conn_alloc_buf_segment(conn, data_sz);
+                       if (!seg) {
+                               log_error("Failed to alloc text buf segment\n");
+                               conn_free_rsp_buf_list(conn);
+                               break;
                        }
                }
-               conn->rsp.data = conn->rsp_buffer;
-       }
-       if (conn->rsp.datasize + len > INCOMING_BUFSIZE) {
-               /* ToDo: multi-PDU replies */
-               log_warning("Dropping key (%s=%s) due to INCOMING_BUFSIZE "
-                       "limit %d and because only single PDU replies during "
-                       "discovery session are implemented. If you have "
-                       "a lot of targets, you can increase INCOMING_BUFSIZE, "
-                       "but, since it will be against iSCSI RFC required "
-                       "not-negotiated PDU limit, not all initiators might "
-                       "work with it. Alternatively, you can decrease names "
-                       "of your targets so they will fit to INCOMING_BUFSIZE "
-                       "limit", key, value, INCOMING_BUFSIZE);
-               return;
-       }
+               switch (stage) {
+               case 0:
+                       sz = min_t(int, data_sz - seg->len, keylen - off);
+                       strncpy(seg->data + seg->len, key + off, sz);
+                       if (sz == data_sz - seg->len) {
+                               off += sz;
+                               if (keylen - off == 0) {
+                                       off = 0;
+                                       stage++;
+                               }
+                       } else {
+                               off = 0;
+                               stage++;
+                       }
+                       break;
+               case 1:
+                       seg->data[seg->len] = '=';
+                       off = 0;
+                       sz = 1;
+                       stage++;
+                       break;
+               case 2:
+                       sz = min_t(int, data_sz - seg->len, valuelen - off);
+                       strncpy(seg->data + seg->len, value + off, sz);
+                       off += sz;
+                       if (valuelen - off == 0) {
+                               off = 0;
+                               stage++;
+                       }
+                       break;
+               case 3:
+                       seg->data[seg->len] = 0;
+                       sz = 1;
+                       break;
+               }
 
-       buffer = conn->rsp_buffer;
-       buffer += conn->rsp.datasize;
-       conn->rsp.datasize += len;
+               log_debug(1, "wrote: %s\n", seg->data + seg->len);
 
-       strcpy(buffer, key);
-       buffer += keylen;
-       *buffer++ = '=';
-       strcpy(buffer, value);
+               seg->len += sz;
+               len -= sz;
+       }
 }
 
 static void text_key_add_reject(struct connection *conn, char *key)
@@ -588,23 +641,43 @@ static void cmnd_exec_login(struct connection *conn)
                rsp->flags |= nsg | (stay ? 0 : ISCSI_FLG_TRANSIT);
        }
 
+       /*
+        * TODO: support Logical Text Data Segments > INCOMING_BUFSIZE (i.e.
+        * key=value pairs spanning several PDUs) during login phase
+        */
+       if (!list_empty(&conn->rsp_buf_list) &&
+           !list_length_is_one(&conn->rsp_buf_list)) {
+               log_error("Target error: \'key=value\' pairs spanning several "
+                         "Login PDUs are not implemented, yet\n");
+               goto target_err;
+       }
+
        rsp->sid = conn->sid;
        rsp->stat_sn = cpu_to_be32(conn->stat_sn++);
        rsp->exp_cmd_sn = cpu_to_be32(conn->exp_cmd_sn);
        rsp->max_cmd_sn = cpu_to_be32(conn->exp_cmd_sn + 1);
        return;
+
 init_err:
        rsp->flags = 0;
        rsp->status_class = ISCSI_STATUS_INITIATOR_ERR;
        rsp->status_detail = ISCSI_STATUS_INIT_ERR;
        conn->state = STATE_EXIT;
        return;
+
 auth_err:
        rsp->flags = 0;
        rsp->status_class = ISCSI_STATUS_INITIATOR_ERR;
        rsp->status_detail = ISCSI_STATUS_AUTH_FAILED;
        conn->state = STATE_EXIT;
        return;
+
+target_err:
+       rsp->flags = 0;
+       rsp->status_class = ISCSI_STATUS_TARGET_ERR;
+       rsp->status_detail = ISCSI_STATUS_TARGET_ERROR;
+       conn->state = STATE_EXIT;
+       return;
 }
 
 static void text_scan_text(struct connection *conn)
@@ -679,22 +752,39 @@ static void cmnd_exec_text(struct connection *conn)
 
        memset(rsp, 0, BHS_SIZE);
 
-       if (be32_to_cpu(req->ttt) != 0xffffffff) {
-               /* reject */;
-       }
        rsp->opcode = ISCSI_OP_TEXT_RSP;
        rsp->itt = req->itt;
-       //rsp->ttt = rsp->ttt;
-       rsp->ttt = 0xffffffff;
        conn->exp_cmd_sn = be32_to_cpu(req->cmd_sn);
        if (!(req->opcode & ISCSI_OP_IMMEDIATE))
                conn->exp_cmd_sn++;
 
        log_debug(1, "Text request: %d", conn->state);
-       text_scan_text(conn);
 
-       if (req->flags & ISCSI_FLG_FINAL)
+       if (req->ttt == ISCSI_RESERVED_TAG) {
+               conn_free_rsp_buf_list(conn);
+               text_scan_text(conn);
+               if (!list_empty(&conn->rsp_buf_list) &&
+                   !list_length_is_one(&conn->rsp_buf_list))
+                       conn->ttt = get_next_ttt(conn);
+               else
+                       conn->ttt = ISCSI_RESERVED_TAG;
+       } else if (list_empty(&conn->rsp_buf_list) || conn->ttt != req->ttt) {
+               log_error("Rejecting unexpected text request. TTT recv %#x, "
+                         "expected %#x; %stext segments queued\n",
+                         req->ttt, conn->ttt, list_empty(&conn->rsp_buf_list) ?
+                         "no " : "");
+               /*
+                * Return a malformed text rsp and close the conn for now.
+                * The proper response would be a Reject instead.
+                */
+               conn->ttt = rsp->ttt = ISCSI_RESERVED_TAG;
+               conn_free_rsp_buf_list(conn);
+               conn->state = STATE_CLOSE;
+       }
+
+       if (list_length_is_one(&conn->rsp_buf_list))
                rsp->flags = ISCSI_FLG_FINAL;
+       rsp->ttt = conn->ttt;
 
        rsp->stat_sn = cpu_to_be32(conn->stat_sn++);
        rsp->exp_cmd_sn = cpu_to_be32(conn->exp_cmd_sn);
@@ -722,11 +812,16 @@ static void cmnd_exec_logout(struct connection *conn)
 int cmnd_execute(struct connection *conn)
 {
        int res = 1;
+       struct buf_segment *seg;
+       struct iscsi_login_rsp_hdr *login_rsp;
 
        switch (conn->req.bhs.opcode & ISCSI_OPCODE_MASK) {
        case ISCSI_OP_LOGIN_CMD:
                //if conn->state == STATE_FULL -> reject
                cmnd_exec_login(conn);
+               login_rsp = (struct iscsi_login_rsp_hdr *) &conn->rsp.bhs;
+               if (login_rsp->status_class)
+                       conn_free_rsp_buf_list(conn);
                break;
        case ISCSI_OP_TEXT_CMD:
                //if conn->state != STATE_FULL -> reject
@@ -742,6 +837,17 @@ int cmnd_execute(struct connection *conn)
                goto out;
        }
 
+       if (!list_empty(&conn->rsp_buf_list)) {
+               seg = list_entry(conn->rsp_buf_list.q_forw,
+                                struct buf_segment, entry);
+               list_del(&seg->entry);
+               conn->rsp.datasize = seg->len;
+               conn->rsp.data = seg->data;
+       } else {
+               conn->rsp.datasize = 0;
+               conn->rsp.data = NULL;
+       }
+
        conn->rsp.bhs.ahslength = conn->rsp.ahssize / 4;
        conn->rsp.bhs.datalength[0] = conn->rsp.datasize >> 16;
        conn->rsp.bhs.datalength[1] = conn->rsp.datasize >> 8;
@@ -754,6 +860,13 @@ out:
 
 void cmnd_finish(struct connection *conn)
 {
+       struct buf_segment *seg;
+
+       if (conn->rsp.data) {
+               seg = container_of(conn->rsp.data, struct buf_segment, data);
+               free(seg);
+       }
+
        switch (conn->state) {
        case STATE_EXIT:
                conn->state = STATE_CLOSE;
index b976873..a3d4d79 100644 (file)
 
 #define PROC_SESSION   "/proc/scsi_tgt/iscsi/session"
 
+struct buf_segment {
+       struct __qelem entry;
+
+       unsigned int len;
+       char data[0];
+};
+
 struct PDU {
        struct iscsi_hdr bhs;
        void *ahs;
@@ -65,6 +72,7 @@ struct connection {
        char *user;
        union iscsi_sid sid;
        u16 cid;
+
        int session_type;
        int auth_method;
 
@@ -73,11 +81,12 @@ struct connection {
 
        u32 cmd_sn;
        u32 exp_cmd_sn;
+       u32 ttt;
 
        struct PDU req;
        void *req_buffer;
        struct PDU rsp;
-       void *rsp_buffer;
+       struct __qelem rsp_buf_list;
        unsigned char *buffer;
        int rwsize;
 
@@ -160,6 +169,7 @@ extern void conn_take_fd(struct connection *conn, int fd);
 extern void conn_read_pdu(struct connection *conn);
 extern void conn_write_pdu(struct connection *conn);
 extern void conn_free_pdu(struct connection *conn);
+extern void conn_free_rsp_buf_list(struct connection *conn);
 
 /* iscsi_scstd.c */
 extern uint16_t server_port;
index 625ad40..5061f1c 100644 (file)
@@ -66,6 +66,36 @@ static inline int list_length_is_one(const struct __qelem *head)
             &pos->member != (head);                                    \
             pos = n, n = list_entry(n->member.q_forw, typeof(*n), member))
 
+#define list_del(elem) remque(elem)
+
+#define list_add(new, head) insque (new, head)
+
+#define list_add_tail(new, head) insque(new, (head)->q_back)
+
+/* min()/max() that do strict type-checking. Lifted from the kernel. */
+#define min(x, y) ({                           \
+       typeof(x) _min1 = (x);                  \
+       typeof(y) _min2 = (y);                  \
+       (void) (&_min1 == &_min2);              \
+       _min1 < _min2 ? _min1 : _min2; })
+
+#define max(x, y) ({                           \
+       typeof(x) _max1 = (x);                  \
+       typeof(y) _max2 = (y);                  \
+       (void) (&_max1 == &_max2);              \
+       _max1 > _max2 ? _max1 : _max2; })
+
+/* ... and their non-checking counterparts, also taken from the kernel. */
+#define min_t(type, x, y) ({                   \
+       type __min1 = (x);                      \
+       type __min2 = (y);                      \
+       __min1 < __min2 ? __min1: __min2; })
+
+#define max_t(type, x, y) ({                   \
+       type __max1 = (x);                      \
+       type __max2 = (y);                      \
+       __max1 > __max2 ? __max1: __max2; })
+
 #ifndef IPV6_V6ONLY
 #define IPV6_V6ONLY    26
 #endif