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