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