xref: /freebsd/usr.bin/iscsictl/iscsictl.c (revision 7fdf597e96a02165cfe22ff357b857d5fa15ed8a)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2012 The FreeBSD Foundation
5  *
6  * This software was developed by Edward Tomasz Napierala under sponsorship
7  * from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  *
30  */
31 
32 #include <sys/cdefs.h>
33 #include <sys/ioctl.h>
34 #include <sys/param.h>
35 #include <sys/linker.h>
36 #include <assert.h>
37 #include <ctype.h>
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <limits.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45 #include <libxo/xo.h>
46 
47 #include <iscsi_ioctl.h>
48 #include "iscsictl.h"
49 
50 struct conf *
51 conf_new(void)
52 {
53 	struct conf *conf;
54 
55 	conf = calloc(1, sizeof(*conf));
56 	if (conf == NULL)
57 		xo_err(1, "calloc");
58 
59 	TAILQ_INIT(&conf->conf_targets);
60 
61 	return (conf);
62 }
63 
64 struct target *
65 target_find(struct conf *conf, const char *nickname)
66 {
67 	struct target *targ;
68 
69 	TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
70 		if (targ->t_nickname != NULL &&
71 		    strcasecmp(targ->t_nickname, nickname) == 0)
72 			return (targ);
73 	}
74 
75 	return (NULL);
76 }
77 
78 struct target *
79 target_new(struct conf *conf)
80 {
81 	struct target *targ;
82 
83 	targ = calloc(1, sizeof(*targ));
84 	if (targ == NULL)
85 		xo_err(1, "calloc");
86 	targ->t_conf = conf;
87 	targ->t_dscp = -1;
88 	targ->t_pcp = -1;
89 	targ->t_pingtimeout = -1;
90 	targ->t_logintimeout = -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 	conf->isc_pcp = targ->t_pcp;
364 	conf->isc_ping_timeout = targ->t_pingtimeout;
365 	conf->isc_login_timeout = targ->t_logintimeout;
366 }
367 
368 static int
369 kernel_add(int iscsi_fd, const struct target *targ)
370 {
371 	struct iscsi_session_add isa;
372 	int error;
373 
374 	memset(&isa, 0, sizeof(isa));
375 	conf_from_target(&isa.isa_conf, targ);
376 	error = ioctl(iscsi_fd, ISCSISADD, &isa);
377 	if (error != 0)
378 		xo_warn("ISCSISADD");
379 	return (error);
380 }
381 
382 static int
383 kernel_modify(int iscsi_fd, unsigned int session_id, const struct target *targ)
384 {
385 	struct iscsi_session_modify ism;
386 	int error;
387 
388 	memset(&ism, 0, sizeof(ism));
389 	ism.ism_session_id = session_id;
390 	conf_from_target(&ism.ism_conf, targ);
391 	error = ioctl(iscsi_fd, ISCSISMODIFY, &ism);
392 	if (error != 0)
393 		xo_warn("ISCSISMODIFY");
394 	return (error);
395 }
396 
397 static void
398 kernel_modify_some(int iscsi_fd, unsigned int session_id, const char *target,
399   const char *target_addr, const char *user, const char *secret, int enable)
400 {
401 	struct iscsi_session_state *states = NULL;
402 	struct iscsi_session_state *state;
403 	struct iscsi_session_conf *conf;
404 	struct iscsi_session_list isl;
405 	struct iscsi_session_modify ism;
406 	unsigned int i, nentries = 1;
407 	int error;
408 
409 	for (;;) {
410 		states = realloc(states,
411 		    nentries * sizeof(struct iscsi_session_state));
412 		if (states == NULL)
413 			xo_err(1, "realloc");
414 
415 		memset(&isl, 0, sizeof(isl));
416 		isl.isl_nentries = nentries;
417 		isl.isl_pstates = states;
418 
419 		error = ioctl(iscsi_fd, ISCSISLIST, &isl);
420 		if (error != 0 && errno == EMSGSIZE) {
421 			nentries *= 4;
422 			continue;
423 		}
424 		break;
425 	}
426 	if (error != 0)
427 		xo_errx(1, "ISCSISLIST");
428 
429 	for (i = 0; i < isl.isl_nentries; i++) {
430 		state = &states[i];
431 
432 		if (state->iss_id == session_id)
433 			break;
434 	}
435 	if (i == isl.isl_nentries)
436 		xo_errx(1, "session-id %u not found", session_id);
437 
438 	conf = &state->iss_conf;
439 
440 	if (target != NULL)
441 		strlcpy(conf->isc_target, target, sizeof(conf->isc_target));
442 	if (target_addr != NULL)
443 		strlcpy(conf->isc_target_addr, target_addr,
444 		    sizeof(conf->isc_target_addr));
445 	if (user != NULL)
446 		strlcpy(conf->isc_user, user, sizeof(conf->isc_user));
447 	if (secret != NULL)
448 		strlcpy(conf->isc_secret, secret, sizeof(conf->isc_secret));
449 	if (enable == ENABLE_ON)
450 		conf->isc_enable = 1;
451 	else if (enable == ENABLE_OFF)
452 		conf->isc_enable = 0;
453 
454 	memset(&ism, 0, sizeof(ism));
455 	ism.ism_session_id = session_id;
456 	memcpy(&ism.ism_conf, conf, sizeof(ism.ism_conf));
457 	error = ioctl(iscsi_fd, ISCSISMODIFY, &ism);
458 	if (error != 0)
459 		xo_warn("ISCSISMODIFY");
460 }
461 
462 static int
463 kernel_remove(int iscsi_fd, const struct target *targ)
464 {
465 	struct iscsi_session_remove isr;
466 	int error;
467 
468 	memset(&isr, 0, sizeof(isr));
469 	conf_from_target(&isr.isr_conf, targ);
470 	error = ioctl(iscsi_fd, ISCSISREMOVE, &isr);
471 	if (error != 0)
472 		xo_warn("ISCSISREMOVE");
473 	return (error);
474 }
475 
476 /*
477  * XXX: Add filtering.
478  */
479 static int
480 kernel_list(int iscsi_fd, const struct target *targ __unused,
481     int verbose)
482 {
483 	struct iscsi_session_state *states = NULL;
484 	const struct iscsi_session_state *state;
485 	const struct iscsi_session_conf *conf;
486 	struct iscsi_session_list isl;
487 	unsigned int i, nentries = 1;
488 	int error;
489 
490 	for (;;) {
491 		states = realloc(states,
492 		    nentries * sizeof(struct iscsi_session_state));
493 		if (states == NULL)
494 			xo_err(1, "realloc");
495 
496 		memset(&isl, 0, sizeof(isl));
497 		isl.isl_nentries = nentries;
498 		isl.isl_pstates = states;
499 
500 		error = ioctl(iscsi_fd, ISCSISLIST, &isl);
501 		if (error != 0 && errno == EMSGSIZE) {
502 			nentries *= 4;
503 			continue;
504 		}
505 		break;
506 	}
507 	if (error != 0) {
508 		xo_warn("ISCSISLIST");
509 		return (error);
510 	}
511 
512 	if (verbose != 0) {
513 		xo_open_list("session");
514 		for (i = 0; i < isl.isl_nentries; i++) {
515 			state = &states[i];
516 			conf = &state->iss_conf;
517 
518 			xo_open_instance("session");
519 
520 			/*
521 			 * Display-only modifier as this information
522 			 * is also present within the 'session' container
523 			 */
524 			xo_emit("{L:/%-26s}{V:sessionId/%u}\n",
525 			    "Session ID:", state->iss_id);
526 
527 			xo_open_container("initiator");
528 			xo_emit("{L:/%-26s}{V:name/%s}\n",
529 			    "Initiator name:", conf->isc_initiator);
530 			xo_emit("{L:/%-26s}{V:portal/%s}\n",
531 			    "Initiator portal:", conf->isc_initiator_addr);
532 			xo_emit("{L:/%-26s}{V:alias/%s}\n",
533 			    "Initiator alias:", conf->isc_initiator_alias);
534 			xo_close_container("initiator");
535 
536 			xo_open_container("target");
537 			xo_emit("{L:/%-26s}{V:name/%s}\n",
538 			    "Target name:", conf->isc_target);
539 			xo_emit("{L:/%-26s}{V:portal/%s}\n",
540 			    "Target portal:", conf->isc_target_addr);
541 			xo_emit("{L:/%-26s}{V:alias/%s}\n",
542 			    "Target alias:", state->iss_target_alias);
543 			if (conf->isc_dscp != -1)
544 				xo_emit("{L:/%-26s}{V:dscp/0x%02x}\n",
545 				    "Target DSCP:", conf->isc_dscp);
546 			if (conf->isc_pcp != -1)
547 				xo_emit("{L:/%-26s}{V:pcp/0x%02x}\n",
548 				    "Target PCP:", conf->isc_pcp);
549 			if (conf->isc_ping_timeout != -1)
550 				xo_emit("{L:/%-26s}{V:PingTimeout/%d}\n",
551 				    "Target PingTimeout:",
552 				    conf->isc_ping_timeout);
553 			if (conf->isc_login_timeout != -1)
554 				xo_emit("{L:/%-26s}{V:LoginTimeout/%d}\n",
555 				    "Target LoginTimeout:",
556 				    conf->isc_login_timeout);
557 			xo_close_container("target");
558 
559 			xo_open_container("auth");
560 			xo_emit("{L:/%-26s}{V:user/%s}\n",
561 			    "User:", conf->isc_user);
562 			xo_emit("{L:/%-26s}{V:secret/%s}\n",
563 			    "Secret:", conf->isc_secret);
564 			xo_emit("{L:/%-26s}{V:mutualUser/%s}\n",
565 			    "Mutual user:", conf->isc_mutual_user);
566 			xo_emit("{L:/%-26s}{V:mutualSecret/%s}\n",
567 			    "Mutual secret:", conf->isc_mutual_secret);
568 			xo_close_container("auth");
569 
570 			xo_emit("{L:/%-26s}{V:type/%s}\n",
571 			    "Session type:",
572 			    conf->isc_discovery ? "Discovery" : "Normal");
573 			xo_emit("{L:/%-26s}{V:enable/%s}\n",
574 			    "Enable:",
575 			    conf->isc_enable ? "Yes" : "No");
576 			xo_emit("{L:/%-26s}{V:state/%s}\n",
577 			    "Session state:",
578 			    state->iss_connected ? "Connected" : "Disconnected");
579 			xo_emit("{L:/%-26s}{V:failureReason/%s}\n",
580 			    "Failure reason:", state->iss_reason);
581 			xo_emit("{L:/%-26s}{V:headerDigest/%s}\n",
582 			    "Header digest:",
583 			    state->iss_header_digest == ISCSI_DIGEST_CRC32C ?
584 			    "CRC32C" : "None");
585 			xo_emit("{L:/%-26s}{V:dataDigest/%s}\n",
586 			    "Data digest:",
587 			    state->iss_data_digest == ISCSI_DIGEST_CRC32C ?
588 			    "CRC32C" : "None");
589 			xo_emit("{L:/%-26s}{V:recvDataSegmentLen/%d}\n",
590 			    "MaxRecvDataSegmentLength:",
591 			    state->iss_max_recv_data_segment_length);
592 			xo_emit("{L:/%-26s}{V:sendDataSegmentLen/%d}\n",
593 			    "MaxSendDataSegmentLength:",
594 			    state->iss_max_send_data_segment_length);
595 			xo_emit("{L:/%-26s}{V:maxBurstLen/%d}\n",
596 			    "MaxBurstLen:", state->iss_max_burst_length);
597 			xo_emit("{L:/%-26s}{V:firstBurstLen/%d}\n",
598 			    "FirstBurstLen:", state->iss_first_burst_length);
599 			xo_emit("{L:/%-26s}{V:immediateData/%s}\n",
600 			    "ImmediateData:", state->iss_immediate_data ? "Yes" : "No");
601 			xo_emit("{L:/%-26s}{V:iSER/%s}\n",
602 			    "iSER (RDMA):", conf->isc_iser ? "Yes" : "No");
603 			xo_emit("{L:/%-26s}{V:offloadDriver/%s}\n",
604 			    "Offload driver:", state->iss_offload);
605 			xo_emit("{L:/%-26s}",
606 			    "Device nodes:");
607 			print_periphs(state->iss_id);
608 			xo_emit("\n\n");
609 			xo_close_instance("session");
610 		}
611 		xo_close_list("session");
612 	} else {
613 		xo_emit("{T:/%-36s} {T:/%-16s} {T:/%s}\n",
614 		    "Target name", "Target portal", "State");
615 
616 		if (isl.isl_nentries != 0)
617 			xo_open_list("session");
618 		for (i = 0; i < isl.isl_nentries; i++) {
619 
620 			state = &states[i];
621 			conf = &state->iss_conf;
622 
623 			xo_open_instance("session");
624 			xo_emit("{V:name/%-36s/%s} {V:portal/%-16s/%s} ",
625 			    conf->isc_target, conf->isc_target_addr);
626 
627 			if (state->iss_reason[0] != '\0' &&
628 			    conf->isc_enable != 0) {
629 				xo_emit("{V:state/%s}\n", state->iss_reason);
630 			} else {
631 				if (conf->isc_discovery) {
632 					xo_emit("{V:state}\n", "Discovery");
633 				} else if (conf->isc_enable == 0) {
634 					xo_emit("{V:state}\n", "Disabled");
635 				} else if (state->iss_connected) {
636 					xo_emit("{V:state}: ", "Connected");
637 					print_periphs(state->iss_id);
638 					xo_emit("\n");
639 				} else {
640 					xo_emit("{V:state}\n", "Disconnected");
641 				}
642 			}
643 			xo_close_instance("session");
644 		}
645 		if (isl.isl_nentries != 0)
646 			xo_close_list("session");
647 	}
648 
649 	return (0);
650 }
651 
652 static int
653 kernel_wait(int iscsi_fd, int timeout)
654 {
655 	struct iscsi_session_state *states = NULL;
656 	const struct iscsi_session_state *state;
657 	struct iscsi_session_list isl;
658 	unsigned int i, nentries = 1;
659 	bool all_connected;
660 	int error;
661 
662 	for (;;) {
663 		for (;;) {
664 			states = realloc(states,
665 			    nentries * sizeof(struct iscsi_session_state));
666 			if (states == NULL)
667 				xo_err(1, "realloc");
668 
669 			memset(&isl, 0, sizeof(isl));
670 			isl.isl_nentries = nentries;
671 			isl.isl_pstates = states;
672 
673 			error = ioctl(iscsi_fd, ISCSISLIST, &isl);
674 			if (error != 0 && errno == EMSGSIZE) {
675 				nentries *= 4;
676 				continue;
677 			}
678 			break;
679 		}
680 		if (error != 0) {
681 			xo_warn("ISCSISLIST");
682 			return (error);
683 		}
684 
685 		all_connected = true;
686 		for (i = 0; i < isl.isl_nentries; i++) {
687 			state = &states[i];
688 
689 			if (!state->iss_connected) {
690 				all_connected = false;
691 				break;
692 			}
693 		}
694 
695 		if (all_connected)
696 			return (0);
697 
698 		sleep(1);
699 
700 		if (timeout > 0) {
701 			timeout--;
702 			if (timeout == 0)
703 				return (1);
704 		}
705 	}
706 }
707 
708 static void
709 usage(void)
710 {
711 
712 	xo_error("usage: iscsictl -A -p portal -t target "
713 	    "[-u user -s secret] [-w timeout] [-e on | off]\n");
714 	xo_error("       iscsictl -A -d discovery-host "
715 	    "[-u user -s secret] [-e on | off]\n");
716 	xo_error("       iscsictl -A -a [-c path]\n");
717 	xo_error("       iscsictl -A -n nickname [-c path]\n");
718 	xo_error("       iscsictl -M -i session-id [-p portal] "
719 	    "[-t target] [-u user] [-s secret] [-e on | off]\n");
720 	xo_error("       iscsictl -M -i session-id -n nickname "
721 	    "[-c path]\n");
722 	xo_error("       iscsictl -R [-p portal] [-t target]\n");
723 	xo_error("       iscsictl -R -a\n");
724 	xo_error("       iscsictl -R -n nickname [-c path]\n");
725 	xo_error("       iscsictl -L [-v] [-w timeout]\n");
726 	exit(1);
727 }
728 
729 int
730 main(int argc, char **argv)
731 {
732 	int Aflag = 0, Mflag = 0, Rflag = 0, Lflag = 0, aflag = 0,
733 	    rflag = 0, vflag = 0;
734 	const char *conf_path = DEFAULT_CONFIG_PATH;
735 	char *nickname = NULL, *discovery_host = NULL, *portal = NULL,
736 	    *target = NULL, *user = NULL, *secret = NULL;
737 	int timeout = -1, enable = ENABLE_UNSPECIFIED;
738 	long long session_id = -1;
739 	char *end;
740 	int ch, error, iscsi_fd, retval, saved_errno;
741 	int failed = 0;
742 	struct conf *conf;
743 	struct target *targ;
744 
745 	argc = xo_parse_args(argc, argv);
746 	if (argc < 0)
747 		exit(1);
748 
749 	xo_set_version(ISCSICTL_XO_VERSION);
750 	xo_open_container("iscsictl");
751 
752 	while ((ch = getopt(argc, argv, "AMRLac:d:e:i:n:p:rt:u:s:vw:")) != -1) {
753 		switch (ch) {
754 		case 'A':
755 			Aflag = 1;
756 			break;
757 		case 'M':
758 			Mflag = 1;
759 			break;
760 		case 'R':
761 			Rflag = 1;
762 			break;
763 		case 'L':
764 			Lflag = 1;
765 			break;
766 		case 'a':
767 			aflag = 1;
768 			break;
769 		case 'c':
770 			conf_path = optarg;
771 			break;
772 		case 'd':
773 			discovery_host = optarg;
774 			break;
775 		case 'e':
776 			enable = parse_enable(optarg);
777 			if (enable == ENABLE_UNSPECIFIED) {
778 				xo_errx(1, "invalid argument to -e, "
779 				    "must be either \"on\" or \"off\"");
780 			}
781 			break;
782 		case 'i':
783 			session_id = strtol(optarg, &end, 10);
784 			if ((size_t)(end - optarg) != strlen(optarg))
785 				xo_errx(1, "trailing characters after session-id");
786 			if (session_id < 0)
787 				xo_errx(1, "session-id cannot be negative");
788 			if (session_id > UINT_MAX)
789 				xo_errx(1, "session-id cannot be greater than %u",
790 				    UINT_MAX);
791 			break;
792 		case 'n':
793 			nickname = optarg;
794 			break;
795 		case 'p':
796 			portal = optarg;
797 			break;
798 		case 'r':
799 			rflag = 1;
800 			break;
801 		case 't':
802 			target = optarg;
803 			break;
804 		case 'u':
805 			user = optarg;
806 			break;
807 		case 's':
808 			secret = optarg;
809 			break;
810 		case 'v':
811 			vflag = 1;
812 			break;
813 		case 'w':
814 			timeout = strtol(optarg, &end, 10);
815 			if ((size_t)(end - optarg) != strlen(optarg))
816 				xo_errx(1, "trailing characters after timeout");
817 			if (timeout < 0)
818 				xo_errx(1, "timeout cannot be negative");
819 			break;
820 		case '?':
821 		default:
822 			usage();
823 		}
824 	}
825 	argc -= optind;
826 	if (argc != 0)
827 		usage();
828 
829 	if (Aflag + Mflag + Rflag + Lflag == 0)
830 		Lflag = 1;
831 	if (Aflag + Mflag + Rflag + Lflag > 1)
832 		xo_errx(1, "at most one of -A, -M, -R, or -L may be specified");
833 
834 	/*
835 	 * Note that we ignore unnecessary/inapplicable "-c" flag; so that
836 	 * people can do something like "alias ISCSICTL="iscsictl -c path"
837 	 * in shell scripts.
838 	 */
839 	if (Aflag != 0) {
840 		if (aflag != 0) {
841 			if (enable != ENABLE_UNSPECIFIED)
842 				xo_errx(1, "-a and -e are mutually exclusive");
843 			if (portal != NULL)
844 				xo_errx(1, "-a and -p are mutually exclusive");
845 			if (target != NULL)
846 				xo_errx(1, "-a and -t are mutually exclusive");
847 			if (user != NULL)
848 				xo_errx(1, "-a and -u are mutually exclusive");
849 			if (secret != NULL)
850 				xo_errx(1, "-a and -s are mutually exclusive");
851 			if (nickname != NULL)
852 				xo_errx(1, "-a and -n are mutually exclusive");
853 			if (discovery_host != NULL)
854 				xo_errx(1, "-a and -d are mutually exclusive");
855 			if (rflag != 0)
856 				xo_errx(1, "-a and -r are mutually exclusive");
857 		} else if (nickname != NULL) {
858 			if (enable != ENABLE_UNSPECIFIED)
859 				xo_errx(1, "-n and -e are mutually exclusive");
860 			if (portal != NULL)
861 				xo_errx(1, "-n and -p are mutually exclusive");
862 			if (target != NULL)
863 				xo_errx(1, "-n and -t are mutually exclusive");
864 			if (user != NULL)
865 				xo_errx(1, "-n and -u are mutually exclusive");
866 			if (secret != NULL)
867 				xo_errx(1, "-n and -s are mutually exclusive");
868 			if (discovery_host != NULL)
869 				xo_errx(1, "-n and -d are mutually exclusive");
870 			if (rflag != 0)
871 				xo_errx(1, "-n and -r are mutually exclusive");
872 		} else if (discovery_host != NULL) {
873 			if (portal != NULL)
874 				xo_errx(1, "-d and -p are mutually exclusive");
875 			if (target != NULL)
876 				xo_errx(1, "-d and -t are mutually exclusive");
877 		} else {
878 			if (target == NULL && portal == NULL)
879 				xo_errx(1, "must specify -a, -n or -t/-p");
880 
881 			if (target != NULL && portal == NULL)
882 				xo_errx(1, "-t must always be used with -p");
883 			if (portal != NULL && target == NULL)
884 				xo_errx(1, "-p must always be used with -t");
885 		}
886 
887 		if (user != NULL && secret == NULL)
888 			xo_errx(1, "-u must always be used with -s");
889 		if (secret != NULL && user == NULL)
890 			xo_errx(1, "-s must always be used with -u");
891 
892 		if (session_id != -1)
893 			xo_errx(1, "-i cannot be used with -A");
894 		if (vflag != 0)
895 			xo_errx(1, "-v cannot be used with -A");
896 
897 	} else if (Mflag != 0) {
898 		if (session_id == -1)
899 			xo_errx(1, "-M requires -i");
900 
901 		if (nickname != NULL) {
902 			if (enable != ENABLE_UNSPECIFIED)
903 				xo_errx(1, "-n and -e are mutually exclusive");
904 			if (portal != NULL)
905 				xo_errx(1, "-n and -p are mutually exclusive");
906 			if (target != NULL)
907 				xo_errx(1, "-n and -t are mutually exclusive");
908 			if (user != NULL)
909 				xo_errx(1, "-n and -u are mutually exclusive");
910 			if (secret != NULL)
911 				xo_errx(1, "-n and -s are mutually exclusive");
912 		}
913 
914 		if (aflag != 0)
915 			xo_errx(1, "-a cannot be used with -M");
916 		if (discovery_host != NULL)
917 			xo_errx(1, "-d cannot be used with -M");
918 		if (rflag != 0)
919 			xo_errx(1, "-r cannot be used with -M");
920 		if (vflag != 0)
921 			xo_errx(1, "-v cannot be used with -M");
922 		if (timeout != -1)
923 			xo_errx(1, "-w cannot be used with -M");
924 
925 	} else if (Rflag != 0) {
926 		if (aflag != 0) {
927 			if (portal != NULL)
928 				xo_errx(1, "-a and -p are mutually exclusive");
929 			if (target != NULL)
930 				xo_errx(1, "-a and -t are mutually exclusive");
931 			if (nickname != NULL)
932 				xo_errx(1, "-a and -n are mutually exclusive");
933 		} else if (nickname != NULL) {
934 			if (portal != NULL)
935 				xo_errx(1, "-n and -p are mutually exclusive");
936 			if (target != NULL)
937 				xo_errx(1, "-n and -t are mutually exclusive");
938 		} else if (target == NULL && portal == NULL) {
939 			xo_errx(1, "must specify either -a, -n, -t, or -p");
940 		}
941 
942 		if (discovery_host != NULL)
943 			xo_errx(1, "-d cannot be used with -R");
944 		if (enable != ENABLE_UNSPECIFIED)
945 			xo_errx(1, "-e cannot be used with -R");
946 		if (session_id != -1)
947 			xo_errx(1, "-i cannot be used with -R");
948 		if (rflag != 0)
949 			xo_errx(1, "-r cannot be used with -R");
950 		if (user != NULL)
951 			xo_errx(1, "-u cannot be used with -R");
952 		if (secret != NULL)
953 			xo_errx(1, "-s cannot be used with -R");
954 		if (vflag != 0)
955 			xo_errx(1, "-v cannot be used with -R");
956 		if (timeout != -1)
957 			xo_errx(1, "-w cannot be used with -R");
958 
959 	} else {
960 		assert(Lflag != 0);
961 
962 		if (discovery_host != NULL)
963 			xo_errx(1, "-d cannot be used with -L");
964 		if (session_id != -1)
965 			xo_errx(1, "-i cannot be used with -L");
966 		if (nickname != NULL)
967 			xo_errx(1, "-n cannot be used with -L");
968 		if (portal != NULL)
969 			xo_errx(1, "-p cannot be used with -L");
970 		if (rflag != 0)
971 			xo_errx(1, "-r cannot be used with -L");
972 		if (target != NULL)
973 			xo_errx(1, "-t cannot be used with -L");
974 		if (user != NULL)
975 			xo_errx(1, "-u cannot be used with -L");
976 		if (secret != NULL)
977 			xo_errx(1, "-s cannot be used with -L");
978 	}
979 
980 	iscsi_fd = open(ISCSI_PATH, O_RDWR);
981 	if (iscsi_fd < 0 && errno == ENOENT) {
982 		saved_errno = errno;
983 		retval = kldload("iscsi");
984 		if (retval != -1)
985 			iscsi_fd = open(ISCSI_PATH, O_RDWR);
986 		else
987 			errno = saved_errno;
988 	}
989 	if (iscsi_fd < 0)
990 		xo_err(1, "failed to open %s", ISCSI_PATH);
991 
992 	if (Aflag != 0 && aflag != 0) {
993 		conf = conf_new_from_file(conf_path);
994 
995 		TAILQ_FOREACH(targ, &conf->conf_targets, t_next)
996 			failed += kernel_add(iscsi_fd, targ);
997 	} else if (nickname != NULL) {
998 		conf = conf_new_from_file(conf_path);
999 		targ = target_find(conf, nickname);
1000 		if (targ == NULL)
1001 			xo_errx(1, "target %s not found in %s",
1002 			    nickname, conf_path);
1003 
1004 		if (Aflag != 0)
1005 			failed += kernel_add(iscsi_fd, targ);
1006 		else if (Mflag != 0)
1007 			failed += kernel_modify(iscsi_fd, session_id, targ);
1008 		else if (Rflag != 0)
1009 			failed += kernel_remove(iscsi_fd, targ);
1010 		else
1011 			failed += kernel_list(iscsi_fd, targ, vflag);
1012 	} else if (Mflag != 0) {
1013 		kernel_modify_some(iscsi_fd, session_id, target, portal,
1014 		    user, secret, enable);
1015 	} else {
1016 		if (Aflag != 0 && target != NULL) {
1017 			if (valid_iscsi_name(target) == false)
1018 				xo_errx(1, "invalid target name \"%s\"", target);
1019 		}
1020 		conf = conf_new();
1021 		targ = target_new(conf);
1022 		targ->t_initiator_name = default_initiator_name();
1023 		targ->t_header_digest = DIGEST_NONE;
1024 		targ->t_data_digest = DIGEST_NONE;
1025 		targ->t_name = target;
1026 		if (discovery_host != NULL) {
1027 			targ->t_session_type = SESSION_TYPE_DISCOVERY;
1028 			targ->t_address = discovery_host;
1029 		} else {
1030 			targ->t_session_type = SESSION_TYPE_NORMAL;
1031 			targ->t_address = portal;
1032 		}
1033 		targ->t_enable = enable;
1034 		if (rflag != 0)
1035 			targ->t_protocol = PROTOCOL_ISER;
1036 		targ->t_user = user;
1037 		targ->t_secret = secret;
1038 
1039 		if (Aflag != 0)
1040 			failed += kernel_add(iscsi_fd, targ);
1041 		else if (Rflag != 0)
1042 			failed += kernel_remove(iscsi_fd, targ);
1043 		else
1044 			failed += kernel_list(iscsi_fd, targ, vflag);
1045 	}
1046 
1047 	if (timeout != -1)
1048 		failed += kernel_wait(iscsi_fd, timeout);
1049 
1050 	error = close(iscsi_fd);
1051 	if (error != 0)
1052 		xo_err(1, "close");
1053 
1054 	xo_close_container("iscsictl");
1055 	if (xo_finish() < 0)
1056 		xo_err(1, "stdout");
1057 
1058 	if (failed != 0)
1059 		exit(1);
1060 
1061 	exit(0);
1062 }
1063