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