iscsi: new module from Ronnie.
[ccan] / ccan / iscsi / pdu.c
1 /* 
2    Copyright (C) 2010 by Ronnie Sahlberg <ronniesahlberg@gmail.com>
3    
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3 of the License, or
7    (at your option) any later version.
8    
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13    
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <strings.h>
21 #include <string.h>
22 #include <arpa/inet.h>
23 #include "iscsi.h"
24 #include "iscsi-private.h"
25 #include "scsi-lowlevel.h"
26 #include "dlinklist.h"
27
28 struct iscsi_pdu *iscsi_allocate_pdu(struct iscsi_context *iscsi, enum iscsi_opcode opcode, enum iscsi_opcode response_opcode)
29 {
30         struct iscsi_pdu *pdu;
31
32         pdu = malloc(sizeof(struct iscsi_pdu));
33         if (pdu == NULL) {
34                 printf("failed to allocate pdu\n");
35                 return NULL;
36         }
37         bzero(pdu, sizeof(struct iscsi_pdu));
38
39         pdu->outdata.size = ISCSI_HEADER_SIZE;
40         pdu->outdata.data = malloc(pdu->outdata.size);
41
42         if (pdu->outdata.data == NULL) {
43                 printf("failed to allocate pdu header\n");
44                 free(pdu);
45                 pdu = NULL;
46                 return NULL;
47         }
48         bzero(pdu->outdata.data, pdu->outdata.size);
49
50         /* opcode */
51         pdu->outdata.data[0] = opcode;
52         pdu->response_opcode = response_opcode;
53
54         /* isid */
55         if (opcode ==ISCSI_PDU_LOGIN_REQUEST) {
56                 memcpy(&pdu->outdata.data[8], &iscsi->isid[0], 6);
57         }
58
59         /* itt */
60         *(uint32_t *)&pdu->outdata.data[16] = htonl(iscsi->itt);
61         pdu->itt = iscsi->itt;
62
63         iscsi->itt++;
64
65         return pdu;
66 }
67
68 void iscsi_free_pdu(struct iscsi_context *iscsi, struct iscsi_pdu *pdu)
69 {
70         if (pdu == NULL) {
71                 printf("trying to free NULL pdu\n");
72                 return;
73         }
74         if (pdu->outdata.data) {
75                 free(pdu->outdata.data);
76                 pdu->outdata.data = NULL;
77         }
78         if (pdu->indata.data) {
79                 free(pdu->indata.data);
80                 pdu->indata.data = NULL;
81         }
82         if (pdu->scsi_cbdata) {
83                 iscsi_free_scsi_cbdata(pdu->scsi_cbdata);
84                 pdu->scsi_cbdata = NULL;
85         }
86
87         free(pdu);
88 }
89
90
91 int iscsi_add_data(struct iscsi_data *data, unsigned char *dptr, int dsize, int pdualignment)
92 {
93         int len, aligned;
94         unsigned char *buf;
95
96         if (dsize == 0) {
97                 printf("Trying to append zero size data to iscsi_data\n");
98                 return -1;
99         }
100
101         len = data->size + dsize;
102         aligned = len;
103         if (pdualignment) {
104                 aligned = (aligned+3)&0xfffffffc;
105         }
106         buf = malloc(aligned);
107         if (buf == NULL) {      
108                 printf("failed to allocate buffer for %d bytes\n", len);
109                 return -2;
110         }
111
112         memcpy(buf, data->data, data->size);
113         memcpy(buf + data->size, dptr, dsize);
114         if (len != aligned) {
115                 /* zero out any padding at the end */
116                bzero(buf+len, aligned-len);
117         }
118
119         free(data->data);
120         data->data  = buf;
121         data->size = len;
122
123         return 0;
124 }
125
126 int iscsi_pdu_add_data(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, unsigned char *dptr, int dsize)
127 {
128         int len, aligned;
129         unsigned char *buf;
130  
131         if (pdu == NULL) {
132                 printf("trying to add data to NULL pdu\n");
133                 return -1;
134         }
135         if (dsize == 0) {
136                 printf("Trying to append zero size data to pdu\n");
137                 return -2;
138         }
139
140         if (iscsi_add_data(&pdu->outdata, dptr, dsize, 1) != 0) {
141                 printf("failed to add data to pdu buffer\n");
142                 return -3;
143         }
144
145         /* update data segment length */
146         *(uint32_t *)&pdu->outdata.data[4] = htonl(pdu->outdata.size-ISCSI_HEADER_SIZE);
147
148         return 0;
149 }
150
151 int iscsi_get_pdu_size(const unsigned char *hdr)
152 {
153         int size;
154
155         size = (ntohl(*(uint32_t *)&hdr[4])&0x00ffffff) + ISCSI_HEADER_SIZE;
156         size = (size+3)&0xfffffffc;
157
158         return size;
159 }
160
161
162 int iscsi_process_pdu(struct iscsi_context *iscsi, const unsigned char *hdr, int size)
163 {
164         uint32_t itt;
165         enum iscsi_opcode opcode;
166         struct iscsi_pdu *pdu;
167         uint8_t ahslen;
168
169         opcode = hdr[0] & 0x3f;
170         ahslen = hdr[4];
171         itt = ntohl(*(uint32_t *)&hdr[16]);
172
173         if (ahslen != 0) {
174                 printf("cant handle expanded headers yet\n");
175                 return -1;
176         }
177
178         for (pdu = iscsi->waitpdu; pdu; pdu = pdu->next) {
179                 enum iscsi_opcode expected_response = pdu->response_opcode;
180                 int is_finished = 1;
181
182                 if (pdu->itt != itt) {
183                         continue;
184                 }
185
186                 /* we have a special case with scsi-command opcodes, the are replied to by either a scsi-response
187                  * or a data-in, or a combination of both.
188                  */
189                 if (opcode == ISCSI_PDU_DATA_IN && expected_response == ISCSI_PDU_SCSI_RESPONSE) {
190                         expected_response = ISCSI_PDU_DATA_IN;
191                 }
192                                 
193                 if (opcode != expected_response) {
194                         printf("Got wrong opcode back for itt:%d  got:%d expected %d\n", itt, opcode, pdu->response_opcode);
195                         return -1;
196                 }
197                 switch (opcode) {
198                 case ISCSI_PDU_LOGIN_RESPONSE:
199                         if (iscsi_process_login_reply(iscsi, pdu, hdr, size) != 0) {
200                                 DLIST_REMOVE(iscsi->waitpdu, pdu);
201                                 iscsi_free_pdu(iscsi, pdu);
202                                 printf("iscsi login reply failed\n");
203                                 return -2;
204                         }
205                         break;
206                 case ISCSI_PDU_TEXT_RESPONSE:
207                         if (iscsi_process_text_reply(iscsi, pdu, hdr, size) != 0) {
208                                 DLIST_REMOVE(iscsi->waitpdu, pdu);
209                                 iscsi_free_pdu(iscsi, pdu);
210                                 printf("iscsi text reply failed\n");
211                                 return -2;
212                         }
213                         break;
214                 case ISCSI_PDU_LOGOUT_RESPONSE:
215                         if (iscsi_process_logout_reply(iscsi, pdu, hdr, size) != 0) {
216                                 DLIST_REMOVE(iscsi->waitpdu, pdu);
217                                 iscsi_free_pdu(iscsi, pdu);
218                                 printf("iscsi logout reply failed\n");
219                                 return -3;
220                         }
221                         break;
222                 case ISCSI_PDU_SCSI_RESPONSE:
223                         if (iscsi_process_scsi_reply(iscsi, pdu, hdr, size) != 0) {
224                                 DLIST_REMOVE(iscsi->waitpdu, pdu);
225                                 iscsi_free_pdu(iscsi, pdu);
226                                 printf("iscsi response reply failed\n");
227                                 return -4;
228                         }
229                         break;
230                 case ISCSI_PDU_DATA_IN:
231                         if (iscsi_process_scsi_data_in(iscsi, pdu, hdr, size, &is_finished) != 0) {
232                                 DLIST_REMOVE(iscsi->waitpdu, pdu);
233                                 iscsi_free_pdu(iscsi, pdu);
234                                 printf("iscsi data in failed\n");
235                                 return -4;
236                         }
237                         break;
238                 case ISCSI_PDU_NOP_IN:
239                         if (iscsi_process_nop_out_reply(iscsi, pdu, hdr, size) != 0) {
240                                 DLIST_REMOVE(iscsi->waitpdu, pdu);
241                                 iscsi_free_pdu(iscsi, pdu);
242                                 printf("iscsi nop-in failed\n");
243                                 return -5;
244                         }
245                         break;
246                 default:
247                         printf("Dont know how to handle opcode %d\n", opcode);
248                         return -2;
249                 }
250
251                 if (is_finished) {
252                         DLIST_REMOVE(iscsi->waitpdu, pdu);
253                         iscsi_free_pdu(iscsi, pdu);
254                 } else {
255                         printf("pdu is not yet finished, let it remain\n");
256                 }
257                 return 0;
258         }
259
260         return 0;
261 }
262
263 void iscsi_pdu_set_pduflags(struct iscsi_pdu *pdu, unsigned char flags)
264 {
265         pdu->outdata.data[1] = flags;
266 }
267
268 void iscsi_pdu_set_immediate(struct iscsi_pdu *pdu)
269 {
270         pdu->outdata.data[0] |= ISCSI_PDU_IMMEDIATE;
271 }
272
273 void iscsi_pdu_set_ttt(struct iscsi_pdu *pdu, uint32_t ttt)
274 {
275         *(uint32_t *)&pdu->outdata.data[20] = htonl(ttt);
276 }
277
278 void iscsi_pdu_set_cmdsn(struct iscsi_pdu *pdu, uint32_t cmdsn)
279 {
280         *(uint32_t *)&pdu->outdata.data[24] = htonl(cmdsn);
281 }
282
283 void iscsi_pdu_set_expstatsn(struct iscsi_pdu *pdu, uint32_t expstatsnsn)
284 {
285         *(uint32_t *)&pdu->outdata.data[28] = htonl(expstatsnsn);
286 }
287
288 void iscsi_pdu_set_cdb(struct iscsi_pdu *pdu, struct scsi_task *task)
289 {
290         bzero(&pdu->outdata.data[32], 16);
291         memcpy(&pdu->outdata.data[32], task->cdb, task->cdb_size);
292 }
293
294 void iscsi_pdu_set_lun(struct iscsi_pdu *pdu, uint32_t lun)
295 {
296         pdu->outdata.data[9] = lun;
297 }
298
299 void iscsi_pdu_set_expxferlen(struct iscsi_pdu *pdu, uint32_t expxferlen)
300 {
301         *(uint32_t *)&pdu->outdata.data[20] = htonl(expxferlen);
302 }