From 4a7cdb58b45c2822d9b38a56f87de047c5cfc9fd Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Fri, 30 Jan 2009 16:25:52 +0000 Subject: [PATCH] Merge with IET r192-193: - 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 Signed-off-by: Arne Redlich git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@650 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- iscsi-scst/usr/conn.c | 19 +++- iscsi-scst/usr/iscsi_hdr.h | 3 + iscsi-scst/usr/iscsid.c | 185 +++++++++++++++++++++++++++++-------- iscsi-scst/usr/iscsid.h | 12 ++- iscsi-scst/usr/misc.h | 30 ++++++ 5 files changed, 208 insertions(+), 41 deletions(-) diff --git a/iscsi-scst/usr/conn.c b/iscsi-scst/usr/conn.c index 85863041f..b392d3c8c 100644 --- a/iscsi-scst/usr/conn.c +++ b/iscsi-scst/usr/conn.c @@ -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); } diff --git a/iscsi-scst/usr/iscsi_hdr.h b/iscsi-scst/usr/iscsi_hdr.h index 572c16818..64b559d60 100644 --- a/iscsi-scst/usr/iscsi_hdr.h +++ b/iscsi-scst/usr/iscsi_hdr.h @@ -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; diff --git a/iscsi-scst/usr/iscsid.c b/iscsi-scst/usr/iscsid.c index c52b98cdb..a60071811 100644 --- a/iscsi-scst/usr/iscsid.c +++ b/iscsi-scst/usr/iscsid.c @@ -26,6 +26,14 @@ #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; + int off = 0; + int sz = 0; + int stage = 0; + size_t data_sz; - 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; + 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; diff --git a/iscsi-scst/usr/iscsid.h b/iscsi-scst/usr/iscsid.h index b976873fb..a3d4d79b5 100644 --- a/iscsi-scst/usr/iscsid.h +++ b/iscsi-scst/usr/iscsid.h @@ -29,6 +29,13 @@ #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; diff --git a/iscsi-scst/usr/misc.h b/iscsi-scst/usr/misc.h index 625ad407c..5061f1cb2 100644 --- a/iscsi-scst/usr/misc.h +++ b/iscsi-scst/usr/misc.h @@ -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