1/*-
2 * Copyright (c) 2012 The FreeBSD Foundation
3 * All rights reserved.
4 *
5 * This software was developed by Edward Tomasz Napierala under sponsorship
6 * from the FreeBSD Foundation.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 *
29 */
30
31#include <sys/cdefs.h>
32__FBSDID("$FreeBSD: head/usr.bin/iscsictl/iscsictl.c 289459 2015-10-17 16:05:42Z trasz $");
33
34#include <sys/ioctl.h>
35#include <sys/param.h>
36#include <sys/linker.h>
37#include <assert.h>
38#include <ctype.h>
39#include <errno.h>
40#include <fcntl.h>
41#include <limits.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45#include <unistd.h>
46#include <libxo/xo.h>
47
48#include <iscsi_ioctl.h>
49#include "iscsictl.h"
50
51struct conf *
52conf_new(void)
53{
54 struct conf *conf;
55
56 conf = calloc(1, sizeof(*conf));
57 if (conf == NULL)
58 xo_err(1, "calloc");
59
60 TAILQ_INIT(&conf->conf_targets);
61
62 return (conf);
63}
64
65struct target *
66target_find(struct conf *conf, const char *nickname)
67{
68 struct target *targ;
69
70 TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
71 if (targ->t_nickname != NULL &&
72 strcasecmp(targ->t_nickname, nickname) == 0)
73 return (targ);
74 }
75
76 return (NULL);
77}
78
79struct target *
80target_new(struct conf *conf)
81{
82 struct target *targ;
83
84 targ = calloc(1, sizeof(*targ));
85 if (targ == NULL)
86 xo_err(1, "calloc");
87 targ->t_conf = conf;
88 TAILQ_INSERT_TAIL(&conf->conf_targets, targ, t_next);
89
90 return (targ);
91}
92
93void
94target_delete(struct target *targ)
95{
96
97 TAILQ_REMOVE(&targ->t_conf->conf_targets, targ, t_next);
98 free(targ);
99}
100
101
102static char *
103default_initiator_name(void)
104{
105 char *name;
106 size_t namelen;
107 int error;
108
109 namelen = _POSIX_HOST_NAME_MAX + strlen(DEFAULT_IQN);
110
111 name = calloc(1, namelen + 1);
112 if (name == NULL)
113 xo_err(1, "calloc");
114 strcpy(name, DEFAULT_IQN);
115 error = gethostname(name + strlen(DEFAULT_IQN),
116 namelen - strlen(DEFAULT_IQN));
117 if (error != 0)
118 xo_err(1, "gethostname");
119
120 return (name);
121}
122
123static bool
124valid_hex(const char ch)
125{
126 switch (ch) {
127 case '0':
128 case '1':
129 case '2':
130 case '3':
131 case '4':
132 case '5':
133 case '6':
134 case '7':
135 case '8':
136 case '9':
137 case 'a':
138 case 'A':
139 case 'b':
140 case 'B':
141 case 'c':
142 case 'C':
143 case 'd':
144 case 'D':
145 case 'e':
146 case 'E':
147 case 'f':
148 case 'F':
149 return (true);
150 default:
151 return (false);
152 }
153}
154
155bool
156valid_iscsi_name(const char *name)
157{
158 int i;
159
160 if (strlen(name) >= MAX_NAME_LEN) {
161 xo_warnx("overlong name for \"%s\"; max length allowed "
162 "by iSCSI specification is %d characters",
163 name, MAX_NAME_LEN);
164 return (false);
165 }
166
167 /*
168 * In the cases below, we don't return an error, just in case the admin
169 * was right, and we're wrong.
170 */
171 if (strncasecmp(name, "iqn.", strlen("iqn.")) == 0) {
172 for (i = strlen("iqn."); name[i] != '\0'; i++) {
173 /*
174 * XXX: We should verify UTF-8 normalisation, as defined
175 * by 3.2.6.2: iSCSI Name Encoding.
176 */
177 if (isalnum(name[i]))
178 continue;
179 if (name[i] == '-' || name[i] == '.' || name[i] == ':')
180 continue;
181 xo_warnx("invalid character \"%c\" in iSCSI name "
182 "\"%s\"; allowed characters are letters, digits, "
183 "'-', '.', and ':'", name[i], name);
184 break;
185 }
186 /*
187 * XXX: Check more stuff: valid date and a valid reversed domain.
188 */
189 } else if (strncasecmp(name, "eui.", strlen("eui.")) == 0) {
190 if (strlen(name) != strlen("eui.") + 16)
191 xo_warnx("invalid iSCSI name \"%s\"; the \"eui.\" "
192 "should be followed by exactly 16 hexadecimal "
193 "digits", name);
194 for (i = strlen("eui."); name[i] != '\0'; i++) {
195 if (!valid_hex(name[i])) {
196 xo_warnx("invalid character \"%c\" in iSCSI "
197 "name \"%s\"; allowed characters are 1-9 "
198 "and A-F", name[i], name);
199 break;
200 }
201 }
202 } else if (strncasecmp(name, "naa.", strlen("naa.")) == 0) {
203 if (strlen(name) > strlen("naa.") + 32)
204 xo_warnx("invalid iSCSI name \"%s\"; the \"naa.\" "
205 "should be followed by at most 32 hexadecimal "
206 "digits", name);
207 for (i = strlen("naa."); name[i] != '\0'; i++) {
208 if (!valid_hex(name[i])) {
209 xo_warnx("invalid character \"%c\" in ISCSI "
210 "name \"%s\"; allowed characters are 1-9 "
211 "and A-F", name[i], name);
212 break;
213 }
214 }
215 } else {
216 xo_warnx("invalid iSCSI name \"%s\"; should start with "
217 "either \".iqn\", \"eui.\", or \"naa.\"",
218 name);
219 }
220 return (true);
221}
222
223void
224conf_verify(struct conf *conf)
225{
226 struct target *targ;
227
228 TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
229 assert(targ->t_nickname != NULL);
230 if (targ->t_session_type == SESSION_TYPE_UNSPECIFIED)
231 targ->t_session_type = SESSION_TYPE_NORMAL;
232 if (targ->t_session_type == SESSION_TYPE_NORMAL &&
233 targ->t_name == NULL)
234 xo_errx(1, "missing TargetName for target \"%s\"",
235 targ->t_nickname);
236 if (targ->t_session_type == SESSION_TYPE_DISCOVERY &&
237 targ->t_name != NULL)
238 xo_errx(1, "cannot specify TargetName for discovery "
239 "sessions for target \"%s\"", targ->t_nickname);
240 if (targ->t_name != NULL) {
241 if (valid_iscsi_name(targ->t_name) == false)
242 xo_errx(1, "invalid target name \"%s\"",
243 targ->t_name);
244 }
245 if (targ->t_protocol == PROTOCOL_UNSPECIFIED)
246 targ->t_protocol = PROTOCOL_ISCSI;
247 if (targ->t_address == NULL)
248 xo_errx(1, "missing TargetAddress for target \"%s\"",
249 targ->t_nickname);
250 if (targ->t_initiator_name == NULL)
251 targ->t_initiator_name = default_initiator_name();
252 if (valid_iscsi_name(targ->t_initiator_name) == false)
253 xo_errx(1, "invalid initiator name \"%s\"",
254 targ->t_initiator_name);
255 if (targ->t_header_digest == DIGEST_UNSPECIFIED)
256 targ->t_header_digest = DIGEST_NONE;
257 if (targ->t_data_digest == DIGEST_UNSPECIFIED)
258 targ->t_data_digest = DIGEST_NONE;
259 if (targ->t_auth_method == AUTH_METHOD_UNSPECIFIED) {
260 if (targ->t_user != NULL || targ->t_secret != NULL ||
261 targ->t_mutual_user != NULL ||
262 targ->t_mutual_secret != NULL)
263 targ->t_auth_method =
264 AUTH_METHOD_CHAP;
265 else
266 targ->t_auth_method =
267 AUTH_METHOD_NONE;
268 }
269 if (targ->t_auth_method == AUTH_METHOD_CHAP) {
270 if (targ->t_user == NULL) {
271 xo_errx(1, "missing chapIName for target \"%s\"",
272 targ->t_nickname);
273 }
274 if (targ->t_secret == NULL)
275 xo_errx(1, "missing chapSecret for target \"%s\"",
276 targ->t_nickname);
277 if (targ->t_mutual_user != NULL ||
278 targ->t_mutual_secret != NULL) {
279 if (targ->t_mutual_user == NULL)
280 xo_errx(1, "missing tgtChapName for "
281 "target \"%s\"", targ->t_nickname);
282 if (targ->t_mutual_secret == NULL)
283 xo_errx(1, "missing tgtChapSecret for "
284 "target \"%s\"", targ->t_nickname);
285 }
286 }
287 }
288}
289
290static void
291conf_from_target(struct iscsi_session_conf *conf,
292 const struct target *targ)
293{
294 memset(conf, 0, sizeof(*conf));
295
296 /*
297 * XXX: Check bounds and return error instead of silently truncating.
298 */
299 if (targ->t_initiator_name != NULL)
300 strlcpy(conf->isc_initiator, targ->t_initiator_name,
301 sizeof(conf->isc_initiator));
302 if (targ->t_initiator_address != NULL)
303 strlcpy(conf->isc_initiator_addr, targ->t_initiator_address,
304 sizeof(conf->isc_initiator_addr));
305 if (targ->t_initiator_alias != NULL)
306 strlcpy(conf->isc_initiator_alias, targ->t_initiator_alias,
307 sizeof(conf->isc_initiator_alias));
308 if (targ->t_name != NULL)
309 strlcpy(conf->isc_target, targ->t_name,
310 sizeof(conf->isc_target));
311 if (targ->t_address != NULL)
312 strlcpy(conf->isc_target_addr, targ->t_address,
313 sizeof(conf->isc_target_addr));
314 if (targ->t_user != NULL)
315 strlcpy(conf->isc_user, targ->t_user,
316 sizeof(conf->isc_user));
317 if (targ->t_secret != NULL)
318 strlcpy(conf->isc_secret, targ->t_secret,
319 sizeof(conf->isc_secret));
320 if (targ->t_mutual_user != NULL)
321 strlcpy(conf->isc_mutual_user, targ->t_mutual_user,
322 sizeof(conf->isc_mutual_user));
323 if (targ->t_mutual_secret != NULL)
324 strlcpy(conf->isc_mutual_secret, targ->t_mutual_secret,
325 sizeof(conf->isc_mutual_secret));
326 if (targ->t_session_type == SESSION_TYPE_DISCOVERY)
327 conf->isc_discovery = 1;
328 if (targ->t_protocol == PROTOCOL_ISER)
329 conf->isc_iser = 1;
330 if (targ->t_offload != NULL)
331 strlcpy(conf->isc_offload, targ->t_offload,
332 sizeof(conf->isc_offload));
333 if (targ->t_header_digest == DIGEST_CRC32C)
334 conf->isc_header_digest = ISCSI_DIGEST_CRC32C;
335 else
336 conf->isc_header_digest = ISCSI_DIGEST_NONE;
337 if (targ->t_data_digest == DIGEST_CRC32C)
338 conf->isc_data_digest = ISCSI_DIGEST_CRC32C;
339 else
340 conf->isc_data_digest = ISCSI_DIGEST_NONE;
341}
342
343static int
344kernel_add(int iscsi_fd, const struct target *targ)
345{
346 struct iscsi_session_add isa;
347 int error;
348
349 memset(&isa, 0, sizeof(isa));
350 conf_from_target(&isa.isa_conf, targ);
351 error = ioctl(iscsi_fd, ISCSISADD, &isa);
352 if (error != 0)
353 xo_warn("ISCSISADD");
354 return (error);
355}
356
357static int
358kernel_modify(int iscsi_fd, unsigned int session_id, const struct target *targ)
359{
360 struct iscsi_session_modify ism;
361 int error;
362
363 memset(&ism, 0, sizeof(ism));
364 ism.ism_session_id = session_id;
365 conf_from_target(&ism.ism_conf, targ);
366 error = ioctl(iscsi_fd, ISCSISMODIFY, &ism);
367 if (error != 0)
368 xo_warn("ISCSISMODIFY");
369 return (error);
370}
371
372static void
373kernel_modify_some(int iscsi_fd, unsigned int session_id, const char *target,
374 const char *target_addr, const char *user, const char *secret)
375{
376 struct iscsi_session_state *states = NULL;
377 struct iscsi_session_state *state;
378 struct iscsi_session_conf *conf;
379 struct iscsi_session_list isl;
380 struct iscsi_session_modify ism;
381 unsigned int i, nentries = 1;
382 int error;
383
384 for (;;) {
385 states = realloc(states,
386 nentries * sizeof(struct iscsi_session_state));
387 if (states == NULL)
388 xo_err(1, "realloc");
389
390 memset(&isl, 0, sizeof(isl));
391 isl.isl_nentries = nentries;
392 isl.isl_pstates = states;
393
394 error = ioctl(iscsi_fd, ISCSISLIST, &isl);
395 if (error != 0 && errno == EMSGSIZE) {
396 nentries *= 4;
397 continue;
398 }
399 break;
400 }
401 if (error != 0)
402 xo_errx(1, "ISCSISLIST");
403
404 for (i = 0; i < isl.isl_nentries; i++) {
405 state = &states[i];
406
407 if (state->iss_id == session_id)
408 break;
409 }
410 if (i == isl.isl_nentries)
411 xo_errx(1, "session-id %u not found", session_id);
412
413 conf = &state->iss_conf;
414
415 if (target != NULL)
416 strlcpy(conf->isc_target, target, sizeof(conf->isc_target));
417 if (target_addr != NULL)
418 strlcpy(conf->isc_target_addr, target_addr,
419 sizeof(conf->isc_target_addr));
420 if (user != NULL)
421 strlcpy(conf->isc_user, user, sizeof(conf->isc_user));
422 if (secret != NULL)
423 strlcpy(conf->isc_secret, secret, sizeof(conf->isc_secret));
424
425 memset(&ism, 0, sizeof(ism));
426 ism.ism_session_id = session_id;
427 memcpy(&ism.ism_conf, conf, sizeof(ism.ism_conf));
428 error = ioctl(iscsi_fd, ISCSISMODIFY, &ism);
429 if (error != 0)
430 xo_warn("ISCSISMODIFY");
431}
432
433static int
434kernel_remove(int iscsi_fd, const struct target *targ)
435{
436 struct iscsi_session_remove isr;
437 int error;
438
439 memset(&isr, 0, sizeof(isr));
440 conf_from_target(&isr.isr_conf, targ);
441 error = ioctl(iscsi_fd, ISCSISREMOVE, &isr);
442 if (error != 0)
443 xo_warn("ISCSISREMOVE");
444 return (error);
445}
446
447/*
448 * XXX: Add filtering.
449 */
450static int
451kernel_list(int iscsi_fd, const struct target *targ __unused,
452 int verbose)
453{
454 struct iscsi_session_state *states = NULL;
455 const struct iscsi_session_state *state;
456 const struct iscsi_session_conf *conf;
457 struct iscsi_session_list isl;
458 unsigned int i, nentries = 1;
459 int error;
460
461 for (;;) {
462 states = realloc(states,
463 nentries * sizeof(struct iscsi_session_state));
464 if (states == NULL)
465 xo_err(1, "realloc");
466
467 memset(&isl, 0, sizeof(isl));
468 isl.isl_nentries = nentries;
469 isl.isl_pstates = states;
470
471 error = ioctl(iscsi_fd, ISCSISLIST, &isl);
472 if (error != 0 && errno == EMSGSIZE) {
473 nentries *= 4;
474 continue;
475 }
476 break;
477 }
478 if (error != 0) {
479 xo_warn("ISCSISLIST");
480 return (error);
481 }
482
483 if (verbose != 0) {
484 xo_open_list("session");
485 for (i = 0; i < isl.isl_nentries; i++) {
486 state = &states[i];
487 conf = &state->iss_conf;
488
489 xo_open_instance("session");
490
491 /*
492 * Display-only modifier as this information
493 * is also present within the 'session' container
494 */
495 xo_emit("{L:/%-18s}{V:sessionId/%u}\n",
496 "Session ID:", state->iss_id);
497
498 xo_open_container("initiator");
499 xo_emit("{L:/%-18s}{V:name/%s}\n",
500 "Initiator name:", conf->isc_initiator);
501 xo_emit("{L:/%-18s}{V:portal/%s}\n",
502 "Initiator portal:", conf->isc_initiator_addr);
503 xo_emit("{L:/%-18s}{V:alias/%s}\n",
504 "Initiator alias:", conf->isc_initiator_alias);
505 xo_close_container("initiator");
506
507 xo_open_container("target");
508 xo_emit("{L:/%-18s}{V:name/%s}\n",
509 "Target name:", conf->isc_target);
510 xo_emit("{L:/%-18s}{V:portal/%s}\n",
511 "Target portal:", conf->isc_target_addr);
512 xo_emit("{L:/%-18s}{V:alias/%s}\n",
513 "Target alias:", state->iss_target_alias);
514 xo_close_container("target");
515
516 xo_open_container("auth");
517 xo_emit("{L:/%-18s}{V:user/%s}\n",
518 "User:", conf->isc_user);
519 xo_emit("{L:/%-18s}{V:secret/%s}\n",
520 "Secret:", conf->isc_secret);
521 xo_emit("{L:/%-18s}{V:mutualUser/%s}\n",
522 "Mutual user:", conf->isc_mutual_user);
523 xo_emit("{L:/%-18s}{V:mutualSecret/%s}\n",
524 "Mutual secret:", conf->isc_mutual_secret);
525 xo_close_container("auth");
526
527 xo_emit("{L:/%-18s}{V:type/%s}\n",
528 "Session type:",
529 conf->isc_discovery ? "Discovery" : "Normal");
530 xo_emit("{L:/%-18s}{V:state/%s}\n",
531 "Session state:",
532 state->iss_connected ? "Connected" : "Disconnected");
533 xo_emit("{L:/%-18s}{V:failureReason/%s}\n",
534 "Failure reason:", state->iss_reason);
535 xo_emit("{L:/%-18s}{V:headerDigest/%s}\n",
536 "Header digest:",
537 state->iss_header_digest == ISCSI_DIGEST_CRC32C ?
538 "CRC32C" : "None");
539 xo_emit("{L:/%-18s}{V:dataDigest/%s}\n",
540 "Data digest:",
541 state->iss_data_digest == ISCSI_DIGEST_CRC32C ?
542 "CRC32C" : "None");
543 xo_emit("{L:/%-18s}{V:dataSegmentLen/%d}\n",
544 "DataSegmentLen:", state->iss_max_data_segment_length);
545 xo_emit("{L:/%-18s}{V:immediateData/%s}\n",
546 "ImmediateData:", state->iss_immediate_data ? "Yes" : "No");
547 xo_emit("{L:/%-18s}{V:iSER/%s}\n",
548 "iSER (RDMA):", conf->isc_iser ? "Yes" : "No");
549 xo_emit("{L:/%-18s}{V:offloadDriver/%s}\n",
550 "Offload driver:", state->iss_offload);
551 xo_emit("{L:/%-18s}",
552 "Device nodes:");
553 print_periphs(state->iss_id);
554 xo_emit("\n\n");
555 xo_close_instance("session");
556 }
557 xo_close_list("session");
558 } else {
559 xo_emit("{T:/%-36s} {T:/%-16s} {T:/%s}\n",
560 "Target name", "Target portal", "State");
561
562 if (isl.isl_nentries != 0)
563 xo_open_list("session");
564 for (i = 0; i < isl.isl_nentries; i++) {
565
566 state = &states[i];
567 conf = &state->iss_conf;
568
569 xo_open_instance("session");
570 xo_emit("{V:name/%-36s/%s} {V:portal/%-16s/%s} ",
571 conf->isc_target, conf->isc_target_addr);
572
573 if (state->iss_reason[0] != '\0') {
574 xo_emit("{V:state/%s}\n", state->iss_reason);
575 } else {
576 if (conf->isc_discovery) {
577 xo_emit("{V:state}\n", "Discovery");
578 } else if (state->iss_connected) {
579 xo_emit("{V:state}: ", "Connected");
580 print_periphs(state->iss_id);
581 xo_emit("\n");
582 } else {
583 xo_emit("{V:state}\n", "Disconnected");
584 }
585 }
586 xo_close_instance("session");
587 }
588 if (isl.isl_nentries != 0)
589 xo_close_list("session");
590 }
591
592 return (0);
593}
594
595static int
596kernel_wait(int iscsi_fd, int timeout)
597{
598 struct iscsi_session_state *states = NULL;
599 const struct iscsi_session_state *state;
600 struct iscsi_session_list isl;
601 unsigned int i, nentries = 1;
602 bool all_connected;
603 int error;
604
605 for (;;) {
606 for (;;) {
607 states = realloc(states,
608 nentries * sizeof(struct iscsi_session_state));
609 if (states == NULL)
610 xo_err(1, "realloc");
611
612 memset(&isl, 0, sizeof(isl));
613 isl.isl_nentries = nentries;
614 isl.isl_pstates = states;
615
616 error = ioctl(iscsi_fd, ISCSISLIST, &isl);
617 if (error != 0 && errno == EMSGSIZE) {
618 nentries *= 4;
619 continue;
620 }
621 break;
622 }
623 if (error != 0) {
624 xo_warn("ISCSISLIST");
625 return (error);
626 }
627
628 all_connected = true;
629 for (i = 0; i < isl.isl_nentries; i++) {
630 state = &states[i];
631
632 if (!state->iss_connected) {
633 all_connected = false;
634 break;
635 }
636 }
637
638 if (all_connected)
639 return (0);
640
641 sleep(1);
642
643 if (timeout > 0) {
644 timeout--;
645 if (timeout == 0)
646 return (1);
647 }
648 }
649}
650
651static void
652usage(void)
653{
654
655 fprintf(stderr, "usage: iscsictl -A -p portal -t target "
656 "[-u user -s secret] [-w timeout]\n");
657 fprintf(stderr, " iscsictl -A -d discovery-host "
658 "[-u user -s secret]\n");
659 fprintf(stderr, " iscsictl -A -a [-c path]\n");
660 fprintf(stderr, " iscsictl -A -n nickname [-c path]\n");
661 fprintf(stderr, " iscsictl -M -i session-id [-p portal] "
662 "[-t target] [-u user] [-s secret]\n");
663 fprintf(stderr, " iscsictl -M -i session-id -n nickname "
664 "[-c path]\n");
665 fprintf(stderr, " iscsictl -R [-p portal] [-t target]\n");
666 fprintf(stderr, " iscsictl -R -a\n");
667 fprintf(stderr, " iscsictl -R -n nickname [-c path]\n");
668 fprintf(stderr, " iscsictl -L [-v] [-w timeout]\n");
669 exit(1);
670}
671
672char *
673checked_strdup(const char *s)
674{
675 char *c;
676
677 c = strdup(s);
678 if (c == NULL)
679 xo_err(1, "strdup");
680 return (c);
681}
682
683int
684main(int argc, char **argv)
685{
686 int Aflag = 0, Mflag = 0, Rflag = 0, Lflag = 0, aflag = 0, vflag = 0;
687 const char *conf_path = DEFAULT_CONFIG_PATH;
688 char *nickname = NULL, *discovery_host = NULL, *portal = NULL,
689 *target = NULL, *user = NULL, *secret = NULL;
690 int timeout = -1;
691 long long session_id = -1;
692 char *end;
693 int ch, error, iscsi_fd, retval, saved_errno;
694 int failed = 0;
695 struct conf *conf;
696 struct target *targ;
697
698 argc = xo_parse_args(argc, argv);
699 xo_open_container("iscsictl");
700
701 while ((ch = getopt(argc, argv, "AMRLac:d:i:n:p:t:u:s:vw:")) != -1) {
702 switch (ch) {
703 case 'A':
704 Aflag = 1;
705 break;
706 case 'M':
707 Mflag = 1;
708 break;
709 case 'R':
710 Rflag = 1;
711 break;
712 case 'L':
713 Lflag = 1;
714 break;
715 case 'a':
716 aflag = 1;
717 break;
718 case 'c':
719 conf_path = optarg;
720 break;
721 case 'd':
722 discovery_host = optarg;
723 break;
724 case 'i':
725 session_id = strtol(optarg, &end, 10);
726 if ((size_t)(end - optarg) != strlen(optarg))
727 xo_errx(1, "trailing characters after session-id");
728 if (session_id < 0)
729 xo_errx(1, "session-id cannot be negative");
730 if (session_id > UINT_MAX)
731 xo_errx(1, "session-id cannot be greater than %u",
732 UINT_MAX);
733 break;
734 case 'n':
735 nickname = optarg;
736 break;
737 case 'p':
738 portal = optarg;
739 break;
740 case 't':
741 target = optarg;
742 break;
743 case 'u':
744 user = optarg;
745 break;
746 case 's':
747 secret = optarg;
748 break;
749 case 'v':
750 vflag = 1;
751 break;
752 case 'w':
753 timeout = strtol(optarg, &end, 10);
754 if ((size_t)(end - optarg) != strlen(optarg))
755 xo_errx(1, "trailing characters after timeout");
756 if (timeout < 0)
757 xo_errx(1, "timeout cannot be negative");
758 break;
759 case '?':
760 default:
761 usage();
762 }
763 }
764 argc -= optind;
765 if (argc != 0)
766 usage();
767
768 if (Aflag + Mflag + Rflag + Lflag == 0)
769 Lflag = 1;
770 if (Aflag + Mflag + Rflag + Lflag > 1)
771 xo_errx(1, "at most one of -A, -M, -R, or -L may be specified");
772
773 /*
774 * Note that we ignore unneccessary/inapplicable "-c" flag; so that
775 * people can do something like "alias ISCSICTL="iscsictl -c path"
776 * in shell scripts.
777 */
778 if (Aflag != 0) {
779 if (aflag != 0) {
780 if (portal != NULL)
781 xo_errx(1, "-a and -p and mutually exclusive");
782 if (target != NULL)
783 xo_errx(1, "-a and -t and mutually exclusive");
784 if (user != NULL)
785 xo_errx(1, "-a and -u and mutually exclusive");
786 if (secret != NULL)
787 xo_errx(1, "-a and -s and mutually exclusive");
788 if (nickname != NULL)
789 xo_errx(1, "-a and -n and mutually exclusive");
790 if (discovery_host != NULL)
791 xo_errx(1, "-a and -d and mutually exclusive");
792 } else if (nickname != NULL) {
793 if (portal != NULL)
794 xo_errx(1, "-n and -p and mutually exclusive");
795 if (target != NULL)
796 xo_errx(1, "-n and -t and mutually exclusive");
797 if (user != NULL)
798 xo_errx(1, "-n and -u and mutually exclusive");
799 if (secret != NULL)
800 xo_errx(1, "-n and -s and mutually exclusive");
801 if (discovery_host != NULL)
802 xo_errx(1, "-n and -d and mutually exclusive");
803 } else if (discovery_host != NULL) {
804 if (portal != NULL)
805 xo_errx(1, "-d and -p and mutually exclusive");
806 if (target != NULL)
807 xo_errx(1, "-d and -t and mutually exclusive");
808 } else {
809 if (target == NULL && portal == NULL)
810 xo_errx(1, "must specify -a, -n or -t/-p");
811
812 if (target != NULL && portal == NULL)
813 xo_errx(1, "-t must always be used with -p");
814 if (portal != NULL && target == NULL)
815 xo_errx(1, "-p must always be used with -t");
816 }
817
818 if (user != NULL && secret == NULL)
819 xo_errx(1, "-u must always be used with -s");
820 if (secret != NULL && user == NULL)
821 xo_errx(1, "-s must always be used with -u");
822
823 if (session_id != -1)
824 xo_errx(1, "-i cannot be used with -A");
825 if (vflag != 0)
826 xo_errx(1, "-v cannot be used with -A");
827
828 } else if (Mflag != 0) {
829 if (session_id == -1)
830 xo_errx(1, "-M requires -i");
831
832 if (discovery_host != NULL)
833 xo_errx(1, "-M and -d are mutually exclusive");
834 if (aflag != 0)
835 xo_errx(1, "-M and -a are mutually exclusive");
836 if (nickname != NULL) {
837 if (portal != NULL)
838 xo_errx(1, "-n and -p and mutually exclusive");
839 if (target != NULL)
840 xo_errx(1, "-n and -t and mutually exclusive");
841 if (user != NULL)
842 xo_errx(1, "-n and -u and mutually exclusive");
843 if (secret != NULL)
844 xo_errx(1, "-n and -s and mutually exclusive");
845 }
846
847 if (vflag != 0)
848 xo_errx(1, "-v cannot be used with -M");
849 if (timeout != -1)
850 xo_errx(1, "-w cannot be used with -M");
851
852 } else if (Rflag != 0) {
853 if (user != NULL)
854 xo_errx(1, "-R and -u are mutually exclusive");
855 if (secret != NULL)
856 xo_errx(1, "-R and -s are mutually exclusive");
857 if (discovery_host != NULL)
858 xo_errx(1, "-R and -d are mutually exclusive");
859
860 if (aflag != 0) {
861 if (portal != NULL)
862 xo_errx(1, "-a and -p and mutually exclusive");
863 if (target != NULL)
864 xo_errx(1, "-a and -t and mutually exclusive");
865 if (nickname != NULL)
866 xo_errx(1, "-a and -n and mutually exclusive");
867 } else if (nickname != NULL) {
868 if (portal != NULL)
869 xo_errx(1, "-n and -p and mutually exclusive");
870 if (target != NULL)
871 xo_errx(1, "-n and -t and mutually exclusive");
872 } else if (target == NULL && portal == NULL) {
873 xo_errx(1, "must specify either -a, -n, -t, or -p");
874 }
875
876 if (session_id != -1)
877 xo_errx(1, "-i cannot be used with -R");
878 if (vflag != 0)
879 xo_errx(1, "-v cannot be used with -R");
880 if (timeout != -1)
881 xo_errx(1, "-w cannot be used with -R");
882
883 } else {
884 assert(Lflag != 0);
885
886 if (portal != NULL)
887 xo_errx(1, "-L and -p and mutually exclusive");
888 if (target != NULL)
889 xo_errx(1, "-L and -t and mutually exclusive");
890 if (user != NULL)
891 xo_errx(1, "-L and -u and mutually exclusive");
892 if (secret != NULL)
893 xo_errx(1, "-L and -s and mutually exclusive");
894 if (nickname != NULL)
895 xo_errx(1, "-L and -n and mutually exclusive");
896 if (discovery_host != NULL)
897 xo_errx(1, "-L and -d and mutually exclusive");
898
899 if (session_id != -1)
900 xo_errx(1, "-i cannot be used with -L");
901 }
902
903 iscsi_fd = open(ISCSI_PATH, O_RDWR);
904 if (iscsi_fd < 0 && errno == ENOENT) {
905 saved_errno = errno;
906 retval = kldload("iscsi");
907 if (retval != -1)
908 iscsi_fd = open(ISCSI_PATH, O_RDWR);
909 else
910 errno = saved_errno;
911 }
912 if (iscsi_fd < 0)
913 xo_err(1, "failed to open %s", ISCSI_PATH);
914
915 if (Aflag != 0 && aflag != 0) {
916 conf = conf_new_from_file(conf_path);
917
918 TAILQ_FOREACH(targ, &conf->conf_targets, t_next)
919 failed += kernel_add(iscsi_fd, targ);
920 } else if (nickname != NULL) {
921 conf = conf_new_from_file(conf_path);
922 targ = target_find(conf, nickname);
923 if (targ == NULL)
924 xo_errx(1, "target %s not found in %s",
925 nickname, conf_path);
926
927 if (Aflag != 0)
928 failed += kernel_add(iscsi_fd, targ);
929 else if (Mflag != 0)
930 failed += kernel_modify(iscsi_fd, session_id, targ);
931 else if (Rflag != 0)
932 failed += kernel_remove(iscsi_fd, targ);
933 else
934 failed += kernel_list(iscsi_fd, targ, vflag);
935 } else if (Mflag != 0) {
936 kernel_modify_some(iscsi_fd, session_id, target, portal,
937 user, secret);
938 } else {
939 if (Aflag != 0 && target != NULL) {
940 if (valid_iscsi_name(target) == false)
941 xo_errx(1, "invalid target name \"%s\"", target);
942 }
943 conf = conf_new();
944 targ = target_new(conf);
945 targ->t_initiator_name = default_initiator_name();
946 targ->t_header_digest = DIGEST_NONE;
947 targ->t_data_digest = DIGEST_NONE;
948 targ->t_name = target;
949 if (discovery_host != NULL) {
950 targ->t_session_type = SESSION_TYPE_DISCOVERY;
951 targ->t_address = discovery_host;
952 } else {
953 targ->t_session_type = SESSION_TYPE_NORMAL;
954 targ->t_address = portal;
955 }
956 targ->t_user = user;
957 targ->t_secret = secret;
958
959 if (Aflag != 0)
960 failed += kernel_add(iscsi_fd, targ);
961 else if (Rflag != 0)
962 failed += kernel_remove(iscsi_fd, targ);
963 else
964 failed += kernel_list(iscsi_fd, targ, vflag);
965 }
966
967 if (timeout != -1)
968 failed += kernel_wait(iscsi_fd, timeout);
969
970 error = close(iscsi_fd);
971 if (error != 0)
972 xo_err(1, "close");
973
974 if (failed > 0)
975 return (1);
976
977 xo_close_container("iscsictl");
978 xo_finish();
979 return (0);
980}
2 * Copyright (c) 2012 The FreeBSD Foundation
3 * All rights reserved.
4 *
5 * This software was developed by Edward Tomasz Napierala under sponsorship
6 * from the FreeBSD Foundation.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 *
29 */
30
31#include <sys/cdefs.h>
32__FBSDID("$FreeBSD: head/usr.bin/iscsictl/iscsictl.c 289459 2015-10-17 16:05:42Z trasz $");
33
34#include <sys/ioctl.h>
35#include <sys/param.h>
36#include <sys/linker.h>
37#include <assert.h>
38#include <ctype.h>
39#include <errno.h>
40#include <fcntl.h>
41#include <limits.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45#include <unistd.h>
46#include <libxo/xo.h>
47
48#include <iscsi_ioctl.h>
49#include "iscsictl.h"
50
51struct conf *
52conf_new(void)
53{
54 struct conf *conf;
55
56 conf = calloc(1, sizeof(*conf));
57 if (conf == NULL)
58 xo_err(1, "calloc");
59
60 TAILQ_INIT(&conf->conf_targets);
61
62 return (conf);
63}
64
65struct target *
66target_find(struct conf *conf, const char *nickname)
67{
68 struct target *targ;
69
70 TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
71 if (targ->t_nickname != NULL &&
72 strcasecmp(targ->t_nickname, nickname) == 0)
73 return (targ);
74 }
75
76 return (NULL);
77}
78
79struct target *
80target_new(struct conf *conf)
81{
82 struct target *targ;
83
84 targ = calloc(1, sizeof(*targ));
85 if (targ == NULL)
86 xo_err(1, "calloc");
87 targ->t_conf = conf;
88 TAILQ_INSERT_TAIL(&conf->conf_targets, targ, t_next);
89
90 return (targ);
91}
92
93void
94target_delete(struct target *targ)
95{
96
97 TAILQ_REMOVE(&targ->t_conf->conf_targets, targ, t_next);
98 free(targ);
99}
100
101
102static char *
103default_initiator_name(void)
104{
105 char *name;
106 size_t namelen;
107 int error;
108
109 namelen = _POSIX_HOST_NAME_MAX + strlen(DEFAULT_IQN);
110
111 name = calloc(1, namelen + 1);
112 if (name == NULL)
113 xo_err(1, "calloc");
114 strcpy(name, DEFAULT_IQN);
115 error = gethostname(name + strlen(DEFAULT_IQN),
116 namelen - strlen(DEFAULT_IQN));
117 if (error != 0)
118 xo_err(1, "gethostname");
119
120 return (name);
121}
122
123static bool
124valid_hex(const char ch)
125{
126 switch (ch) {
127 case '0':
128 case '1':
129 case '2':
130 case '3':
131 case '4':
132 case '5':
133 case '6':
134 case '7':
135 case '8':
136 case '9':
137 case 'a':
138 case 'A':
139 case 'b':
140 case 'B':
141 case 'c':
142 case 'C':
143 case 'd':
144 case 'D':
145 case 'e':
146 case 'E':
147 case 'f':
148 case 'F':
149 return (true);
150 default:
151 return (false);
152 }
153}
154
155bool
156valid_iscsi_name(const char *name)
157{
158 int i;
159
160 if (strlen(name) >= MAX_NAME_LEN) {
161 xo_warnx("overlong name for \"%s\"; max length allowed "
162 "by iSCSI specification is %d characters",
163 name, MAX_NAME_LEN);
164 return (false);
165 }
166
167 /*
168 * In the cases below, we don't return an error, just in case the admin
169 * was right, and we're wrong.
170 */
171 if (strncasecmp(name, "iqn.", strlen("iqn.")) == 0) {
172 for (i = strlen("iqn."); name[i] != '\0'; i++) {
173 /*
174 * XXX: We should verify UTF-8 normalisation, as defined
175 * by 3.2.6.2: iSCSI Name Encoding.
176 */
177 if (isalnum(name[i]))
178 continue;
179 if (name[i] == '-' || name[i] == '.' || name[i] == ':')
180 continue;
181 xo_warnx("invalid character \"%c\" in iSCSI name "
182 "\"%s\"; allowed characters are letters, digits, "
183 "'-', '.', and ':'", name[i], name);
184 break;
185 }
186 /*
187 * XXX: Check more stuff: valid date and a valid reversed domain.
188 */
189 } else if (strncasecmp(name, "eui.", strlen("eui.")) == 0) {
190 if (strlen(name) != strlen("eui.") + 16)
191 xo_warnx("invalid iSCSI name \"%s\"; the \"eui.\" "
192 "should be followed by exactly 16 hexadecimal "
193 "digits", name);
194 for (i = strlen("eui."); name[i] != '\0'; i++) {
195 if (!valid_hex(name[i])) {
196 xo_warnx("invalid character \"%c\" in iSCSI "
197 "name \"%s\"; allowed characters are 1-9 "
198 "and A-F", name[i], name);
199 break;
200 }
201 }
202 } else if (strncasecmp(name, "naa.", strlen("naa.")) == 0) {
203 if (strlen(name) > strlen("naa.") + 32)
204 xo_warnx("invalid iSCSI name \"%s\"; the \"naa.\" "
205 "should be followed by at most 32 hexadecimal "
206 "digits", name);
207 for (i = strlen("naa."); name[i] != '\0'; i++) {
208 if (!valid_hex(name[i])) {
209 xo_warnx("invalid character \"%c\" in ISCSI "
210 "name \"%s\"; allowed characters are 1-9 "
211 "and A-F", name[i], name);
212 break;
213 }
214 }
215 } else {
216 xo_warnx("invalid iSCSI name \"%s\"; should start with "
217 "either \".iqn\", \"eui.\", or \"naa.\"",
218 name);
219 }
220 return (true);
221}
222
223void
224conf_verify(struct conf *conf)
225{
226 struct target *targ;
227
228 TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
229 assert(targ->t_nickname != NULL);
230 if (targ->t_session_type == SESSION_TYPE_UNSPECIFIED)
231 targ->t_session_type = SESSION_TYPE_NORMAL;
232 if (targ->t_session_type == SESSION_TYPE_NORMAL &&
233 targ->t_name == NULL)
234 xo_errx(1, "missing TargetName for target \"%s\"",
235 targ->t_nickname);
236 if (targ->t_session_type == SESSION_TYPE_DISCOVERY &&
237 targ->t_name != NULL)
238 xo_errx(1, "cannot specify TargetName for discovery "
239 "sessions for target \"%s\"", targ->t_nickname);
240 if (targ->t_name != NULL) {
241 if (valid_iscsi_name(targ->t_name) == false)
242 xo_errx(1, "invalid target name \"%s\"",
243 targ->t_name);
244 }
245 if (targ->t_protocol == PROTOCOL_UNSPECIFIED)
246 targ->t_protocol = PROTOCOL_ISCSI;
247 if (targ->t_address == NULL)
248 xo_errx(1, "missing TargetAddress for target \"%s\"",
249 targ->t_nickname);
250 if (targ->t_initiator_name == NULL)
251 targ->t_initiator_name = default_initiator_name();
252 if (valid_iscsi_name(targ->t_initiator_name) == false)
253 xo_errx(1, "invalid initiator name \"%s\"",
254 targ->t_initiator_name);
255 if (targ->t_header_digest == DIGEST_UNSPECIFIED)
256 targ->t_header_digest = DIGEST_NONE;
257 if (targ->t_data_digest == DIGEST_UNSPECIFIED)
258 targ->t_data_digest = DIGEST_NONE;
259 if (targ->t_auth_method == AUTH_METHOD_UNSPECIFIED) {
260 if (targ->t_user != NULL || targ->t_secret != NULL ||
261 targ->t_mutual_user != NULL ||
262 targ->t_mutual_secret != NULL)
263 targ->t_auth_method =
264 AUTH_METHOD_CHAP;
265 else
266 targ->t_auth_method =
267 AUTH_METHOD_NONE;
268 }
269 if (targ->t_auth_method == AUTH_METHOD_CHAP) {
270 if (targ->t_user == NULL) {
271 xo_errx(1, "missing chapIName for target \"%s\"",
272 targ->t_nickname);
273 }
274 if (targ->t_secret == NULL)
275 xo_errx(1, "missing chapSecret for target \"%s\"",
276 targ->t_nickname);
277 if (targ->t_mutual_user != NULL ||
278 targ->t_mutual_secret != NULL) {
279 if (targ->t_mutual_user == NULL)
280 xo_errx(1, "missing tgtChapName for "
281 "target \"%s\"", targ->t_nickname);
282 if (targ->t_mutual_secret == NULL)
283 xo_errx(1, "missing tgtChapSecret for "
284 "target \"%s\"", targ->t_nickname);
285 }
286 }
287 }
288}
289
290static void
291conf_from_target(struct iscsi_session_conf *conf,
292 const struct target *targ)
293{
294 memset(conf, 0, sizeof(*conf));
295
296 /*
297 * XXX: Check bounds and return error instead of silently truncating.
298 */
299 if (targ->t_initiator_name != NULL)
300 strlcpy(conf->isc_initiator, targ->t_initiator_name,
301 sizeof(conf->isc_initiator));
302 if (targ->t_initiator_address != NULL)
303 strlcpy(conf->isc_initiator_addr, targ->t_initiator_address,
304 sizeof(conf->isc_initiator_addr));
305 if (targ->t_initiator_alias != NULL)
306 strlcpy(conf->isc_initiator_alias, targ->t_initiator_alias,
307 sizeof(conf->isc_initiator_alias));
308 if (targ->t_name != NULL)
309 strlcpy(conf->isc_target, targ->t_name,
310 sizeof(conf->isc_target));
311 if (targ->t_address != NULL)
312 strlcpy(conf->isc_target_addr, targ->t_address,
313 sizeof(conf->isc_target_addr));
314 if (targ->t_user != NULL)
315 strlcpy(conf->isc_user, targ->t_user,
316 sizeof(conf->isc_user));
317 if (targ->t_secret != NULL)
318 strlcpy(conf->isc_secret, targ->t_secret,
319 sizeof(conf->isc_secret));
320 if (targ->t_mutual_user != NULL)
321 strlcpy(conf->isc_mutual_user, targ->t_mutual_user,
322 sizeof(conf->isc_mutual_user));
323 if (targ->t_mutual_secret != NULL)
324 strlcpy(conf->isc_mutual_secret, targ->t_mutual_secret,
325 sizeof(conf->isc_mutual_secret));
326 if (targ->t_session_type == SESSION_TYPE_DISCOVERY)
327 conf->isc_discovery = 1;
328 if (targ->t_protocol == PROTOCOL_ISER)
329 conf->isc_iser = 1;
330 if (targ->t_offload != NULL)
331 strlcpy(conf->isc_offload, targ->t_offload,
332 sizeof(conf->isc_offload));
333 if (targ->t_header_digest == DIGEST_CRC32C)
334 conf->isc_header_digest = ISCSI_DIGEST_CRC32C;
335 else
336 conf->isc_header_digest = ISCSI_DIGEST_NONE;
337 if (targ->t_data_digest == DIGEST_CRC32C)
338 conf->isc_data_digest = ISCSI_DIGEST_CRC32C;
339 else
340 conf->isc_data_digest = ISCSI_DIGEST_NONE;
341}
342
343static int
344kernel_add(int iscsi_fd, const struct target *targ)
345{
346 struct iscsi_session_add isa;
347 int error;
348
349 memset(&isa, 0, sizeof(isa));
350 conf_from_target(&isa.isa_conf, targ);
351 error = ioctl(iscsi_fd, ISCSISADD, &isa);
352 if (error != 0)
353 xo_warn("ISCSISADD");
354 return (error);
355}
356
357static int
358kernel_modify(int iscsi_fd, unsigned int session_id, const struct target *targ)
359{
360 struct iscsi_session_modify ism;
361 int error;
362
363 memset(&ism, 0, sizeof(ism));
364 ism.ism_session_id = session_id;
365 conf_from_target(&ism.ism_conf, targ);
366 error = ioctl(iscsi_fd, ISCSISMODIFY, &ism);
367 if (error != 0)
368 xo_warn("ISCSISMODIFY");
369 return (error);
370}
371
372static void
373kernel_modify_some(int iscsi_fd, unsigned int session_id, const char *target,
374 const char *target_addr, const char *user, const char *secret)
375{
376 struct iscsi_session_state *states = NULL;
377 struct iscsi_session_state *state;
378 struct iscsi_session_conf *conf;
379 struct iscsi_session_list isl;
380 struct iscsi_session_modify ism;
381 unsigned int i, nentries = 1;
382 int error;
383
384 for (;;) {
385 states = realloc(states,
386 nentries * sizeof(struct iscsi_session_state));
387 if (states == NULL)
388 xo_err(1, "realloc");
389
390 memset(&isl, 0, sizeof(isl));
391 isl.isl_nentries = nentries;
392 isl.isl_pstates = states;
393
394 error = ioctl(iscsi_fd, ISCSISLIST, &isl);
395 if (error != 0 && errno == EMSGSIZE) {
396 nentries *= 4;
397 continue;
398 }
399 break;
400 }
401 if (error != 0)
402 xo_errx(1, "ISCSISLIST");
403
404 for (i = 0; i < isl.isl_nentries; i++) {
405 state = &states[i];
406
407 if (state->iss_id == session_id)
408 break;
409 }
410 if (i == isl.isl_nentries)
411 xo_errx(1, "session-id %u not found", session_id);
412
413 conf = &state->iss_conf;
414
415 if (target != NULL)
416 strlcpy(conf->isc_target, target, sizeof(conf->isc_target));
417 if (target_addr != NULL)
418 strlcpy(conf->isc_target_addr, target_addr,
419 sizeof(conf->isc_target_addr));
420 if (user != NULL)
421 strlcpy(conf->isc_user, user, sizeof(conf->isc_user));
422 if (secret != NULL)
423 strlcpy(conf->isc_secret, secret, sizeof(conf->isc_secret));
424
425 memset(&ism, 0, sizeof(ism));
426 ism.ism_session_id = session_id;
427 memcpy(&ism.ism_conf, conf, sizeof(ism.ism_conf));
428 error = ioctl(iscsi_fd, ISCSISMODIFY, &ism);
429 if (error != 0)
430 xo_warn("ISCSISMODIFY");
431}
432
433static int
434kernel_remove(int iscsi_fd, const struct target *targ)
435{
436 struct iscsi_session_remove isr;
437 int error;
438
439 memset(&isr, 0, sizeof(isr));
440 conf_from_target(&isr.isr_conf, targ);
441 error = ioctl(iscsi_fd, ISCSISREMOVE, &isr);
442 if (error != 0)
443 xo_warn("ISCSISREMOVE");
444 return (error);
445}
446
447/*
448 * XXX: Add filtering.
449 */
450static int
451kernel_list(int iscsi_fd, const struct target *targ __unused,
452 int verbose)
453{
454 struct iscsi_session_state *states = NULL;
455 const struct iscsi_session_state *state;
456 const struct iscsi_session_conf *conf;
457 struct iscsi_session_list isl;
458 unsigned int i, nentries = 1;
459 int error;
460
461 for (;;) {
462 states = realloc(states,
463 nentries * sizeof(struct iscsi_session_state));
464 if (states == NULL)
465 xo_err(1, "realloc");
466
467 memset(&isl, 0, sizeof(isl));
468 isl.isl_nentries = nentries;
469 isl.isl_pstates = states;
470
471 error = ioctl(iscsi_fd, ISCSISLIST, &isl);
472 if (error != 0 && errno == EMSGSIZE) {
473 nentries *= 4;
474 continue;
475 }
476 break;
477 }
478 if (error != 0) {
479 xo_warn("ISCSISLIST");
480 return (error);
481 }
482
483 if (verbose != 0) {
484 xo_open_list("session");
485 for (i = 0; i < isl.isl_nentries; i++) {
486 state = &states[i];
487 conf = &state->iss_conf;
488
489 xo_open_instance("session");
490
491 /*
492 * Display-only modifier as this information
493 * is also present within the 'session' container
494 */
495 xo_emit("{L:/%-18s}{V:sessionId/%u}\n",
496 "Session ID:", state->iss_id);
497
498 xo_open_container("initiator");
499 xo_emit("{L:/%-18s}{V:name/%s}\n",
500 "Initiator name:", conf->isc_initiator);
501 xo_emit("{L:/%-18s}{V:portal/%s}\n",
502 "Initiator portal:", conf->isc_initiator_addr);
503 xo_emit("{L:/%-18s}{V:alias/%s}\n",
504 "Initiator alias:", conf->isc_initiator_alias);
505 xo_close_container("initiator");
506
507 xo_open_container("target");
508 xo_emit("{L:/%-18s}{V:name/%s}\n",
509 "Target name:", conf->isc_target);
510 xo_emit("{L:/%-18s}{V:portal/%s}\n",
511 "Target portal:", conf->isc_target_addr);
512 xo_emit("{L:/%-18s}{V:alias/%s}\n",
513 "Target alias:", state->iss_target_alias);
514 xo_close_container("target");
515
516 xo_open_container("auth");
517 xo_emit("{L:/%-18s}{V:user/%s}\n",
518 "User:", conf->isc_user);
519 xo_emit("{L:/%-18s}{V:secret/%s}\n",
520 "Secret:", conf->isc_secret);
521 xo_emit("{L:/%-18s}{V:mutualUser/%s}\n",
522 "Mutual user:", conf->isc_mutual_user);
523 xo_emit("{L:/%-18s}{V:mutualSecret/%s}\n",
524 "Mutual secret:", conf->isc_mutual_secret);
525 xo_close_container("auth");
526
527 xo_emit("{L:/%-18s}{V:type/%s}\n",
528 "Session type:",
529 conf->isc_discovery ? "Discovery" : "Normal");
530 xo_emit("{L:/%-18s}{V:state/%s}\n",
531 "Session state:",
532 state->iss_connected ? "Connected" : "Disconnected");
533 xo_emit("{L:/%-18s}{V:failureReason/%s}\n",
534 "Failure reason:", state->iss_reason);
535 xo_emit("{L:/%-18s}{V:headerDigest/%s}\n",
536 "Header digest:",
537 state->iss_header_digest == ISCSI_DIGEST_CRC32C ?
538 "CRC32C" : "None");
539 xo_emit("{L:/%-18s}{V:dataDigest/%s}\n",
540 "Data digest:",
541 state->iss_data_digest == ISCSI_DIGEST_CRC32C ?
542 "CRC32C" : "None");
543 xo_emit("{L:/%-18s}{V:dataSegmentLen/%d}\n",
544 "DataSegmentLen:", state->iss_max_data_segment_length);
545 xo_emit("{L:/%-18s}{V:immediateData/%s}\n",
546 "ImmediateData:", state->iss_immediate_data ? "Yes" : "No");
547 xo_emit("{L:/%-18s}{V:iSER/%s}\n",
548 "iSER (RDMA):", conf->isc_iser ? "Yes" : "No");
549 xo_emit("{L:/%-18s}{V:offloadDriver/%s}\n",
550 "Offload driver:", state->iss_offload);
551 xo_emit("{L:/%-18s}",
552 "Device nodes:");
553 print_periphs(state->iss_id);
554 xo_emit("\n\n");
555 xo_close_instance("session");
556 }
557 xo_close_list("session");
558 } else {
559 xo_emit("{T:/%-36s} {T:/%-16s} {T:/%s}\n",
560 "Target name", "Target portal", "State");
561
562 if (isl.isl_nentries != 0)
563 xo_open_list("session");
564 for (i = 0; i < isl.isl_nentries; i++) {
565
566 state = &states[i];
567 conf = &state->iss_conf;
568
569 xo_open_instance("session");
570 xo_emit("{V:name/%-36s/%s} {V:portal/%-16s/%s} ",
571 conf->isc_target, conf->isc_target_addr);
572
573 if (state->iss_reason[0] != '\0') {
574 xo_emit("{V:state/%s}\n", state->iss_reason);
575 } else {
576 if (conf->isc_discovery) {
577 xo_emit("{V:state}\n", "Discovery");
578 } else if (state->iss_connected) {
579 xo_emit("{V:state}: ", "Connected");
580 print_periphs(state->iss_id);
581 xo_emit("\n");
582 } else {
583 xo_emit("{V:state}\n", "Disconnected");
584 }
585 }
586 xo_close_instance("session");
587 }
588 if (isl.isl_nentries != 0)
589 xo_close_list("session");
590 }
591
592 return (0);
593}
594
595static int
596kernel_wait(int iscsi_fd, int timeout)
597{
598 struct iscsi_session_state *states = NULL;
599 const struct iscsi_session_state *state;
600 struct iscsi_session_list isl;
601 unsigned int i, nentries = 1;
602 bool all_connected;
603 int error;
604
605 for (;;) {
606 for (;;) {
607 states = realloc(states,
608 nentries * sizeof(struct iscsi_session_state));
609 if (states == NULL)
610 xo_err(1, "realloc");
611
612 memset(&isl, 0, sizeof(isl));
613 isl.isl_nentries = nentries;
614 isl.isl_pstates = states;
615
616 error = ioctl(iscsi_fd, ISCSISLIST, &isl);
617 if (error != 0 && errno == EMSGSIZE) {
618 nentries *= 4;
619 continue;
620 }
621 break;
622 }
623 if (error != 0) {
624 xo_warn("ISCSISLIST");
625 return (error);
626 }
627
628 all_connected = true;
629 for (i = 0; i < isl.isl_nentries; i++) {
630 state = &states[i];
631
632 if (!state->iss_connected) {
633 all_connected = false;
634 break;
635 }
636 }
637
638 if (all_connected)
639 return (0);
640
641 sleep(1);
642
643 if (timeout > 0) {
644 timeout--;
645 if (timeout == 0)
646 return (1);
647 }
648 }
649}
650
651static void
652usage(void)
653{
654
655 fprintf(stderr, "usage: iscsictl -A -p portal -t target "
656 "[-u user -s secret] [-w timeout]\n");
657 fprintf(stderr, " iscsictl -A -d discovery-host "
658 "[-u user -s secret]\n");
659 fprintf(stderr, " iscsictl -A -a [-c path]\n");
660 fprintf(stderr, " iscsictl -A -n nickname [-c path]\n");
661 fprintf(stderr, " iscsictl -M -i session-id [-p portal] "
662 "[-t target] [-u user] [-s secret]\n");
663 fprintf(stderr, " iscsictl -M -i session-id -n nickname "
664 "[-c path]\n");
665 fprintf(stderr, " iscsictl -R [-p portal] [-t target]\n");
666 fprintf(stderr, " iscsictl -R -a\n");
667 fprintf(stderr, " iscsictl -R -n nickname [-c path]\n");
668 fprintf(stderr, " iscsictl -L [-v] [-w timeout]\n");
669 exit(1);
670}
671
672char *
673checked_strdup(const char *s)
674{
675 char *c;
676
677 c = strdup(s);
678 if (c == NULL)
679 xo_err(1, "strdup");
680 return (c);
681}
682
683int
684main(int argc, char **argv)
685{
686 int Aflag = 0, Mflag = 0, Rflag = 0, Lflag = 0, aflag = 0, vflag = 0;
687 const char *conf_path = DEFAULT_CONFIG_PATH;
688 char *nickname = NULL, *discovery_host = NULL, *portal = NULL,
689 *target = NULL, *user = NULL, *secret = NULL;
690 int timeout = -1;
691 long long session_id = -1;
692 char *end;
693 int ch, error, iscsi_fd, retval, saved_errno;
694 int failed = 0;
695 struct conf *conf;
696 struct target *targ;
697
698 argc = xo_parse_args(argc, argv);
699 xo_open_container("iscsictl");
700
701 while ((ch = getopt(argc, argv, "AMRLac:d:i:n:p:t:u:s:vw:")) != -1) {
702 switch (ch) {
703 case 'A':
704 Aflag = 1;
705 break;
706 case 'M':
707 Mflag = 1;
708 break;
709 case 'R':
710 Rflag = 1;
711 break;
712 case 'L':
713 Lflag = 1;
714 break;
715 case 'a':
716 aflag = 1;
717 break;
718 case 'c':
719 conf_path = optarg;
720 break;
721 case 'd':
722 discovery_host = optarg;
723 break;
724 case 'i':
725 session_id = strtol(optarg, &end, 10);
726 if ((size_t)(end - optarg) != strlen(optarg))
727 xo_errx(1, "trailing characters after session-id");
728 if (session_id < 0)
729 xo_errx(1, "session-id cannot be negative");
730 if (session_id > UINT_MAX)
731 xo_errx(1, "session-id cannot be greater than %u",
732 UINT_MAX);
733 break;
734 case 'n':
735 nickname = optarg;
736 break;
737 case 'p':
738 portal = optarg;
739 break;
740 case 't':
741 target = optarg;
742 break;
743 case 'u':
744 user = optarg;
745 break;
746 case 's':
747 secret = optarg;
748 break;
749 case 'v':
750 vflag = 1;
751 break;
752 case 'w':
753 timeout = strtol(optarg, &end, 10);
754 if ((size_t)(end - optarg) != strlen(optarg))
755 xo_errx(1, "trailing characters after timeout");
756 if (timeout < 0)
757 xo_errx(1, "timeout cannot be negative");
758 break;
759 case '?':
760 default:
761 usage();
762 }
763 }
764 argc -= optind;
765 if (argc != 0)
766 usage();
767
768 if (Aflag + Mflag + Rflag + Lflag == 0)
769 Lflag = 1;
770 if (Aflag + Mflag + Rflag + Lflag > 1)
771 xo_errx(1, "at most one of -A, -M, -R, or -L may be specified");
772
773 /*
774 * Note that we ignore unneccessary/inapplicable "-c" flag; so that
775 * people can do something like "alias ISCSICTL="iscsictl -c path"
776 * in shell scripts.
777 */
778 if (Aflag != 0) {
779 if (aflag != 0) {
780 if (portal != NULL)
781 xo_errx(1, "-a and -p and mutually exclusive");
782 if (target != NULL)
783 xo_errx(1, "-a and -t and mutually exclusive");
784 if (user != NULL)
785 xo_errx(1, "-a and -u and mutually exclusive");
786 if (secret != NULL)
787 xo_errx(1, "-a and -s and mutually exclusive");
788 if (nickname != NULL)
789 xo_errx(1, "-a and -n and mutually exclusive");
790 if (discovery_host != NULL)
791 xo_errx(1, "-a and -d and mutually exclusive");
792 } else if (nickname != NULL) {
793 if (portal != NULL)
794 xo_errx(1, "-n and -p and mutually exclusive");
795 if (target != NULL)
796 xo_errx(1, "-n and -t and mutually exclusive");
797 if (user != NULL)
798 xo_errx(1, "-n and -u and mutually exclusive");
799 if (secret != NULL)
800 xo_errx(1, "-n and -s and mutually exclusive");
801 if (discovery_host != NULL)
802 xo_errx(1, "-n and -d and mutually exclusive");
803 } else if (discovery_host != NULL) {
804 if (portal != NULL)
805 xo_errx(1, "-d and -p and mutually exclusive");
806 if (target != NULL)
807 xo_errx(1, "-d and -t and mutually exclusive");
808 } else {
809 if (target == NULL && portal == NULL)
810 xo_errx(1, "must specify -a, -n or -t/-p");
811
812 if (target != NULL && portal == NULL)
813 xo_errx(1, "-t must always be used with -p");
814 if (portal != NULL && target == NULL)
815 xo_errx(1, "-p must always be used with -t");
816 }
817
818 if (user != NULL && secret == NULL)
819 xo_errx(1, "-u must always be used with -s");
820 if (secret != NULL && user == NULL)
821 xo_errx(1, "-s must always be used with -u");
822
823 if (session_id != -1)
824 xo_errx(1, "-i cannot be used with -A");
825 if (vflag != 0)
826 xo_errx(1, "-v cannot be used with -A");
827
828 } else if (Mflag != 0) {
829 if (session_id == -1)
830 xo_errx(1, "-M requires -i");
831
832 if (discovery_host != NULL)
833 xo_errx(1, "-M and -d are mutually exclusive");
834 if (aflag != 0)
835 xo_errx(1, "-M and -a are mutually exclusive");
836 if (nickname != NULL) {
837 if (portal != NULL)
838 xo_errx(1, "-n and -p and mutually exclusive");
839 if (target != NULL)
840 xo_errx(1, "-n and -t and mutually exclusive");
841 if (user != NULL)
842 xo_errx(1, "-n and -u and mutually exclusive");
843 if (secret != NULL)
844 xo_errx(1, "-n and -s and mutually exclusive");
845 }
846
847 if (vflag != 0)
848 xo_errx(1, "-v cannot be used with -M");
849 if (timeout != -1)
850 xo_errx(1, "-w cannot be used with -M");
851
852 } else if (Rflag != 0) {
853 if (user != NULL)
854 xo_errx(1, "-R and -u are mutually exclusive");
855 if (secret != NULL)
856 xo_errx(1, "-R and -s are mutually exclusive");
857 if (discovery_host != NULL)
858 xo_errx(1, "-R and -d are mutually exclusive");
859
860 if (aflag != 0) {
861 if (portal != NULL)
862 xo_errx(1, "-a and -p and mutually exclusive");
863 if (target != NULL)
864 xo_errx(1, "-a and -t and mutually exclusive");
865 if (nickname != NULL)
866 xo_errx(1, "-a and -n and mutually exclusive");
867 } else if (nickname != NULL) {
868 if (portal != NULL)
869 xo_errx(1, "-n and -p and mutually exclusive");
870 if (target != NULL)
871 xo_errx(1, "-n and -t and mutually exclusive");
872 } else if (target == NULL && portal == NULL) {
873 xo_errx(1, "must specify either -a, -n, -t, or -p");
874 }
875
876 if (session_id != -1)
877 xo_errx(1, "-i cannot be used with -R");
878 if (vflag != 0)
879 xo_errx(1, "-v cannot be used with -R");
880 if (timeout != -1)
881 xo_errx(1, "-w cannot be used with -R");
882
883 } else {
884 assert(Lflag != 0);
885
886 if (portal != NULL)
887 xo_errx(1, "-L and -p and mutually exclusive");
888 if (target != NULL)
889 xo_errx(1, "-L and -t and mutually exclusive");
890 if (user != NULL)
891 xo_errx(1, "-L and -u and mutually exclusive");
892 if (secret != NULL)
893 xo_errx(1, "-L and -s and mutually exclusive");
894 if (nickname != NULL)
895 xo_errx(1, "-L and -n and mutually exclusive");
896 if (discovery_host != NULL)
897 xo_errx(1, "-L and -d and mutually exclusive");
898
899 if (session_id != -1)
900 xo_errx(1, "-i cannot be used with -L");
901 }
902
903 iscsi_fd = open(ISCSI_PATH, O_RDWR);
904 if (iscsi_fd < 0 && errno == ENOENT) {
905 saved_errno = errno;
906 retval = kldload("iscsi");
907 if (retval != -1)
908 iscsi_fd = open(ISCSI_PATH, O_RDWR);
909 else
910 errno = saved_errno;
911 }
912 if (iscsi_fd < 0)
913 xo_err(1, "failed to open %s", ISCSI_PATH);
914
915 if (Aflag != 0 && aflag != 0) {
916 conf = conf_new_from_file(conf_path);
917
918 TAILQ_FOREACH(targ, &conf->conf_targets, t_next)
919 failed += kernel_add(iscsi_fd, targ);
920 } else if (nickname != NULL) {
921 conf = conf_new_from_file(conf_path);
922 targ = target_find(conf, nickname);
923 if (targ == NULL)
924 xo_errx(1, "target %s not found in %s",
925 nickname, conf_path);
926
927 if (Aflag != 0)
928 failed += kernel_add(iscsi_fd, targ);
929 else if (Mflag != 0)
930 failed += kernel_modify(iscsi_fd, session_id, targ);
931 else if (Rflag != 0)
932 failed += kernel_remove(iscsi_fd, targ);
933 else
934 failed += kernel_list(iscsi_fd, targ, vflag);
935 } else if (Mflag != 0) {
936 kernel_modify_some(iscsi_fd, session_id, target, portal,
937 user, secret);
938 } else {
939 if (Aflag != 0 && target != NULL) {
940 if (valid_iscsi_name(target) == false)
941 xo_errx(1, "invalid target name \"%s\"", target);
942 }
943 conf = conf_new();
944 targ = target_new(conf);
945 targ->t_initiator_name = default_initiator_name();
946 targ->t_header_digest = DIGEST_NONE;
947 targ->t_data_digest = DIGEST_NONE;
948 targ->t_name = target;
949 if (discovery_host != NULL) {
950 targ->t_session_type = SESSION_TYPE_DISCOVERY;
951 targ->t_address = discovery_host;
952 } else {
953 targ->t_session_type = SESSION_TYPE_NORMAL;
954 targ->t_address = portal;
955 }
956 targ->t_user = user;
957 targ->t_secret = secret;
958
959 if (Aflag != 0)
960 failed += kernel_add(iscsi_fd, targ);
961 else if (Rflag != 0)
962 failed += kernel_remove(iscsi_fd, targ);
963 else
964 failed += kernel_list(iscsi_fd, targ, vflag);
965 }
966
967 if (timeout != -1)
968 failed += kernel_wait(iscsi_fd, timeout);
969
970 error = close(iscsi_fd);
971 if (error != 0)
972 xo_err(1, "close");
973
974 if (failed > 0)
975 return (1);
976
977 xo_close_container("iscsictl");
978 xo_finish();
979 return (0);
980}