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