1 /*
2    iscsi test-tool multipath support
3 
4    Copyright (C) 2015 David Disseldorp
5 
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10 
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "config.h"
21 
22 #define _GNU_SOURCE
23 #include <assert.h>
24 #include <sys/syscall.h>
25 #include <dlfcn.h>
26 #include <sys/types.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <stdint.h>
30 #include <stdarg.h>
31 #include <inttypes.h>
32 #include <string.h>
33 #include <poll.h>
34 #include <fnmatch.h>
35 #include <errno.h>
36 
37 #ifdef HAVE_SG_IO
38 #include <fcntl.h>
39 #include <sys/ioctl.h>
40 #include <scsi/sg.h>
41 #endif
42 
43 #include "slist.h"
44 #include "iscsi.h"
45 #include "scsi-lowlevel.h"
46 #include "iscsi-private.h"
47 #include "iscsi-support.h"
48 #include "iscsi-multipath.h"
49 
50 int mp_num_sds = 0;
51 struct scsi_device *mp_sds[MPATH_MAX_DEVS];
52 
53 static void
mpath_des_free(struct scsi_inquiry_device_designator * des)54 mpath_des_free(struct scsi_inquiry_device_designator *des)
55 {
56         if (!des) {
57                 return;
58         }
59 
60         free(des->designator);
61         free(des);
62 }
63 
64 static int
mpath_des_copy(struct scsi_inquiry_device_designator * des,struct scsi_inquiry_device_designator ** _des_cp)65 mpath_des_copy(struct scsi_inquiry_device_designator *des,
66                struct scsi_inquiry_device_designator **_des_cp)
67 {
68         struct scsi_inquiry_device_designator *des_cp;
69 
70         if (!_des_cp) {
71                 return -1;
72         }
73 
74         des_cp = malloc(sizeof(*des_cp));
75         if (des_cp == NULL) {
76                 return -1;
77         }
78 
79         des_cp->protocol_identifier = des->protocol_identifier;
80         des_cp->code_set = des->code_set;
81         des_cp->piv = des->piv;
82         des_cp->association = des->association;
83         des_cp->designator_type = des->designator_type;
84         des_cp->designator_length = des->designator_length;
85         des_cp->designator = malloc(des->designator_length);
86         if (des_cp->designator == NULL) {
87                 free(des_cp);
88                 return -1;
89         }
90         memcpy(des_cp->designator, des->designator, des->designator_length);
91         *_des_cp = des_cp;
92 
93         return 0;
94 }
95 
96 static int
mpath_des_cmp(struct scsi_inquiry_device_designator * des1,struct scsi_inquiry_device_designator * des2)97 mpath_des_cmp(struct scsi_inquiry_device_designator *des1,
98               struct scsi_inquiry_device_designator *des2)
99 {
100         if (des1->protocol_identifier != des2->protocol_identifier) {
101                 return -1;
102         }
103 
104         if (des1->code_set != des2->code_set) {
105                 return -1;
106         }
107 
108         if (des1->piv != des2->piv) {
109                 return -1;
110         }
111 
112         if (des1->association != des2->association) {
113                 return -1;
114         }
115 
116         if (des1->designator_type != des2->designator_type) {
117                 return -1;
118         }
119 
120         if (des1->designator_length != des2->designator_length) {
121                 return -1;
122         }
123 
124         return memcmp(des1->designator, des2->designator,
125                       des1->designator_length);
126 }
127 
128 static int
mpath_check_matching_ids_devid_vpd(int num_sds,struct scsi_device ** sds)129 mpath_check_matching_ids_devid_vpd(int num_sds,
130                                    struct scsi_device **sds)
131 {
132         int i;
133         int num_sds_with_valid_id = 0;
134         struct scsi_task *inq_task = NULL;
135         struct scsi_inquiry_device_designator *des_saved = NULL;
136 
137         for (i = 0; i < num_sds; i++) {
138                 int ret;
139                 int full_size;
140                 struct scsi_inquiry_device_identification *inq_id_data;
141                 struct scsi_inquiry_device_designator *des;
142 
143                 /*
144                  * dev ID inquiry to confirm that all multipath devices carry
145                  * an identical logical unit identifier.
146                  */
147                 inquiry(sds[i], &inq_task, 1,
148                         SCSI_INQUIRY_PAGECODE_DEVICE_IDENTIFICATION,
149                         64,
150                         EXPECT_STATUS_GOOD);
151                 if (inq_task == NULL || inq_task->status != SCSI_STATUS_GOOD) {
152                         printf("Inquiry command failed : %s\n",
153                                sds[i]->error_str);
154                         goto err_cleanup;
155                 }
156                 full_size = scsi_datain_getfullsize(inq_task);
157                 if (full_size > inq_task->datain.size) {
158                         /* we need more data */
159                         scsi_free_scsi_task(inq_task);
160                         inq_task = NULL;
161                         inquiry(sds[i], &inq_task, 1,
162                                 SCSI_INQUIRY_PAGECODE_DEVICE_IDENTIFICATION,
163                                 full_size,
164                                 EXPECT_STATUS_GOOD);
165                         if (inq_task == NULL) {
166                                 printf("Inquiry command failed : %s\n",
167                                        sds[i]->error_str);
168                                 goto err_cleanup;
169                         }
170                 }
171 
172                 inq_id_data = scsi_datain_unmarshall(inq_task);
173                 if (inq_id_data == NULL) {
174                         printf("failed to unmarshall inquiry ID datain blob\n");
175                         goto err_cleanup;
176                 }
177 
178                 if (inq_id_data->qualifier
179                                != SCSI_INQUIRY_PERIPHERAL_QUALIFIER_CONNECTED) {
180                         printf("error: multipath device not connected\n");
181                         goto err_cleanup;
182                 }
183 
184                 if (inq_id_data->device_type
185                          != SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS) {
186                         printf("error: multipath devices must be SBC\n");
187                         goto err_cleanup;
188                 }
189 
190                 /* walk the list of IDs, and find a suitable LU candidate */
191                 for (des = inq_id_data->designators;
192                      des != NULL;
193                      des = des->next) {
194                         if (des->association != SCSI_ASSOCIATION_LOGICAL_UNIT) {
195                                 printf("skipping non-LU designator: %d\n",
196                                        des->association);
197                                 continue;
198                         }
199 
200                         if ((des->designator_type != SCSI_DESIGNATOR_TYPE_EUI_64)
201                          && (des->designator_type != SCSI_DESIGNATOR_TYPE_NAA)
202                          && (des->designator_type != SCSI_DESIGNATOR_TYPE_MD5_LOGICAL_UNIT_IDENTIFIER)
203                          && (des->designator_type != SCSI_DESIGNATOR_TYPE_SCSI_NAME_STRING)) {
204                                 printf("skipping unsupported des type: %d\n",
205                                        des->designator_type);
206                                 continue;
207                         }
208 
209                         if (des->designator_length <= 0) {
210                                 printf("skipping designator with bad len: %d\n",
211                                        des->designator_length);
212                                 continue;
213                         }
214 
215                         if (des_saved == NULL) {
216                                 ret = mpath_des_copy(des, &des_saved);
217                                 if (ret < 0) {
218                                         goto err_cleanup;
219                                 }
220                                 /*
221                                  * we now have a reference to look for in all
222                                  * subsequent paths.
223                                  */
224                                 num_sds_with_valid_id++;
225                                 break;
226                         } else if (mpath_des_cmp(des, des_saved) == 0) {
227                                 /* found match for previous path designator */
228                                 num_sds_with_valid_id++;
229                                 break;
230                         }
231                         /* no match yet, keep checking other designators */
232                 }
233 
234                 scsi_free_scsi_task(inq_task);
235                 inq_task = NULL;
236         }
237         mpath_des_free(des_saved);
238 
239         if (num_sds_with_valid_id != num_sds) {
240                 printf("failed to find matching LU device ID for all paths\n");
241                 return -1;
242         }
243 
244         printf("found matching LU device identifier for all (%d) paths\n",
245                num_sds);
246         return 0;
247 
248 err_cleanup:
249         mpath_des_free(des_saved);
250         scsi_free_scsi_task(inq_task);
251         return -1;
252 }
253 
254 static int
mpath_check_matching_ids_serial_vpd(int num_sds,struct scsi_device ** sds)255 mpath_check_matching_ids_serial_vpd(int num_sds,
256                                     struct scsi_device **sds)
257 {
258         int i;
259         int num_sds_with_valid_id = 0;
260         struct scsi_task *inq_task = NULL;
261         char *usn_saved = NULL;
262 
263         for (i = 0; i < num_sds; i++) {
264                 int full_size;
265                 struct scsi_inquiry_unit_serial_number *inq_serial;
266 
267                 /*
268                  * inquiry to confirm that all multipath devices carry an
269                  * identical unit serial number.
270                  */
271                 inq_task = NULL;
272                 inquiry(sds[i], &inq_task, 1,
273                         SCSI_INQUIRY_PAGECODE_UNIT_SERIAL_NUMBER, 64,
274                         EXPECT_STATUS_GOOD);
275                 if (inq_task == NULL || inq_task->status != SCSI_STATUS_GOOD) {
276                         printf("Inquiry command failed : %s\n",
277                                sds[i]->error_str);
278                         goto err_cleanup;
279                 }
280                 full_size = scsi_datain_getfullsize(inq_task);
281                 if (full_size > inq_task->datain.size) {
282                         scsi_free_scsi_task(inq_task);
283 
284                         /* we need more data */
285                         inq_task = NULL;
286                         inquiry(sds[i], &inq_task, 1,
287                                 SCSI_INQUIRY_PAGECODE_UNIT_SERIAL_NUMBER,
288                                 full_size,
289                                 EXPECT_STATUS_GOOD);
290                         if (inq_task == NULL) {
291                                 printf("Inquiry command failed : %s\n",
292                                        sds[i]->error_str);
293                                 goto err_cleanup;
294                         }
295                 }
296 
297                 inq_serial = scsi_datain_unmarshall(inq_task);
298                 if (inq_serial == NULL) {
299                         printf("failed to unmarshall inquiry datain blob\n");
300                         goto err_cleanup;
301                 }
302 
303                 if (inq_serial->qualifier
304                                != SCSI_INQUIRY_PERIPHERAL_QUALIFIER_CONNECTED) {
305                         printf("error: multipath device not connected\n");
306                         goto err_cleanup;
307                 }
308 
309                 if (inq_serial->device_type
310                          != SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS) {
311                         printf("error: multipath devices must be SBC\n");
312                         goto err_cleanup;
313                 }
314 
315                 if (inq_serial->usn == NULL) {
316                         printf("error: empty usn for multipath device\n");
317                         goto err_cleanup;
318                 }
319 
320                 if (usn_saved == NULL) {
321                         usn_saved = strdup(inq_serial->usn);
322                         if (usn_saved == NULL) {
323                                 goto err_cleanup;
324                         }
325                         num_sds_with_valid_id++;
326                 } else if (strcmp(usn_saved, inq_serial->usn) == 0) {
327                         num_sds_with_valid_id++;
328                 } else {
329                         printf("multipath unit serial mismatch: %s != %s\n",
330                                usn_saved, inq_serial->usn);
331                 }
332 
333                 scsi_free_scsi_task(inq_task);
334                 inq_task = NULL;
335         }
336 
337         if (num_sds_with_valid_id != num_sds) {
338                 printf("failed to find matching serial number for all paths\n");
339                 goto err_cleanup;
340         }
341 
342         printf("found matching serial number for all (%d) paths: %s\n",
343                num_sds, usn_saved);
344         free(usn_saved);
345 
346         return 0;
347 
348 err_cleanup:
349         free(usn_saved);
350         scsi_free_scsi_task(inq_task);
351         return -1;
352 }
353 
354 int
mpath_check_matching_ids(int num_sds,struct scsi_device ** sds)355 mpath_check_matching_ids(int num_sds,
356                          struct scsi_device **sds)
357 {
358         int ret;
359 
360         /*
361          * first check all devices for a matching LU identifier in the device
362          * identification INQUIRY VPD page.
363          */
364         ret = mpath_check_matching_ids_devid_vpd(num_sds, sds);
365         if (ret == 0) {
366                 return 0;        /* found matching */
367         }
368 
369         /* fall back to a unit serial number check */
370         ret = mpath_check_matching_ids_serial_vpd(num_sds, sds);
371         return ret;
372 }
373 
374 int
mpath_count_iscsi(int num_sds,struct scsi_device ** sds)375 mpath_count_iscsi(int num_sds,
376                   struct scsi_device **sds)
377 {
378         int i;
379         int found = 0;
380 
381         for (i = 0; i < num_sds; i++) {
382                 if (sds[i]->iscsi_ctx != NULL) {
383                         found++;
384                 }
385         }
386 
387         return found;
388 }
389 
390 /*
391  * use an existing multi-path connection, or clone iscsi sd1.
392  */
393 int
mpath_sd2_get_or_clone(struct scsi_device * sd1,struct scsi_device ** _sd2)394 mpath_sd2_get_or_clone(struct scsi_device *sd1, struct scsi_device **_sd2)
395 {
396         struct scsi_device *sd2;
397 
398         if (mp_num_sds > 1) {
399                 logging(LOG_VERBOSE, "using multipath dev for second session");
400                 *_sd2 = mp_sds[1];
401                 return 0;
402         }
403 
404         if (sd1->iscsi_ctx == NULL) {
405                 logging(LOG_NORMAL, "can't clone non-iscsi device");
406                 return -EINVAL;
407         }
408 
409         logging(LOG_VERBOSE, "cloning sd1 for second session");
410         sd2 = malloc(sizeof(*sd2));
411         if (sd2 == NULL) {
412                 return -ENOMEM;
413         }
414 
415         memset(sd2, 0, sizeof(*sd2));
416         sd2->iscsi_url = sd1->iscsi_url;
417         sd2->iscsi_lun = sd1->iscsi_lun;
418         sd2->iscsi_ctx = iscsi_context_login(initiatorname2, sd2->iscsi_url,
419                                              &sd2->iscsi_lun);
420         if (sd2->iscsi_ctx == NULL) {
421                 logging(LOG_VERBOSE, "Failed to login to target");
422                 free(sd2);
423                 return -ENOMEM;
424         }
425         *_sd2 = sd2;
426 
427         return 0;
428 }
429 
430 void
mpath_sd2_put(struct scsi_device * sd2)431 mpath_sd2_put(struct scsi_device *sd2)
432 {
433         if (mp_num_sds > 1) {
434                 if (sd2 != mp_sds[1]) {
435                         logging(LOG_NORMAL, "Invalid sd2!");
436                 }
437                 return;
438         }
439 
440         /* sd2 was allocated by mp_get - cleanup */
441         iscsi_logout_sync(sd2->iscsi_ctx);
442         iscsi_destroy_context(sd2->iscsi_ctx);
443         free(sd2);
444 }
445