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