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