xref: /freebsd/usr.bin/iscsictl/iscsictl.c (revision 907b59d76938e654f0d040a888e8dfca3de1e222)
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:/%-18s}{V:sessionId/%u}\n",
518 			    "Session ID:", state->iss_id);
519 
520 			xo_open_container("initiator");
521 			xo_emit("{L:/%-18s}{V:name/%s}\n",
522 			    "Initiator name:", conf->isc_initiator);
523 			xo_emit("{L:/%-18s}{V:portal/%s}\n",
524 			    "Initiator portal:", conf->isc_initiator_addr);
525 			xo_emit("{L:/%-18s}{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:/%-18s}{V:name/%s}\n",
531 			    "Target name:", conf->isc_target);
532 			xo_emit("{L:/%-18s}{V:portal/%s}\n",
533 			    "Target portal:", conf->isc_target_addr);
534 			xo_emit("{L:/%-18s}{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:/%-18s}{V:user/%s}\n",
540 			    "User:", conf->isc_user);
541 			xo_emit("{L:/%-18s}{V:secret/%s}\n",
542 			    "Secret:", conf->isc_secret);
543 			xo_emit("{L:/%-18s}{V:mutualUser/%s}\n",
544 			    "Mutual user:", conf->isc_mutual_user);
545 			xo_emit("{L:/%-18s}{V:mutualSecret/%s}\n",
546 			    "Mutual secret:", conf->isc_mutual_secret);
547 			xo_close_container("auth");
548 
549 			xo_emit("{L:/%-18s}{V:type/%s}\n",
550 			    "Session type:",
551 			    conf->isc_discovery ? "Discovery" : "Normal");
552 			xo_emit("{L:/%-18s}{V:enable/%s}\n",
553 			    "Enable:",
554 			    conf->isc_enable ? "Yes" : "No");
555 			xo_emit("{L:/%-18s}{V:state/%s}\n",
556 			    "Session state:",
557 			    state->iss_connected ? "Connected" : "Disconnected");
558 			xo_emit("{L:/%-18s}{V:failureReason/%s}\n",
559 			    "Failure reason:", state->iss_reason);
560 			xo_emit("{L:/%-18s}{V:headerDigest/%s}\n",
561 			    "Header digest:",
562 			    state->iss_header_digest == ISCSI_DIGEST_CRC32C ?
563 			    "CRC32C" : "None");
564 			xo_emit("{L:/%-18s}{V:dataDigest/%s}\n",
565 			    "Data digest:",
566 			    state->iss_data_digest == ISCSI_DIGEST_CRC32C ?
567 			    "CRC32C" : "None");
568 			xo_emit("{L:/%-18s}{V:dataSegmentLen/%d}\n",
569 			    "DataSegmentLen:", state->iss_max_data_segment_length);
570 			xo_emit("{L:/%-18s}{V:maxBurstLen/%d}\n",
571 			    "MaxBurstLen:", state->iss_max_burst_length);
572 			xo_emit("{L:/%-18s}{V:firstBurstLen/%d}\n",
573 			    "FirstBurstLen:", state->iss_first_burst_length);
574 			xo_emit("{L:/%-18s}{V:immediateData/%s}\n",
575 			    "ImmediateData:", state->iss_immediate_data ? "Yes" : "No");
576 			xo_emit("{L:/%-18s}{V:iSER/%s}\n",
577 			    "iSER (RDMA):", conf->isc_iser ? "Yes" : "No");
578 			xo_emit("{L:/%-18s}{V:offloadDriver/%s}\n",
579 			    "Offload driver:", state->iss_offload);
580 			xo_emit("{L:/%-18s}",
581 			    "Device nodes:");
582 			print_periphs(state->iss_id);
583 			xo_emit("\n\n");
584 			xo_close_instance("session");
585 		}
586 		xo_close_list("session");
587 	} else {
588 		xo_emit("{T:/%-36s} {T:/%-16s} {T:/%s}\n",
589 		    "Target name", "Target portal", "State");
590 
591 		if (isl.isl_nentries != 0)
592 			xo_open_list("session");
593 		for (i = 0; i < isl.isl_nentries; i++) {
594 
595 			state = &states[i];
596 			conf = &state->iss_conf;
597 
598 			xo_open_instance("session");
599 			xo_emit("{V:name/%-36s/%s} {V:portal/%-16s/%s} ",
600 			    conf->isc_target, conf->isc_target_addr);
601 
602 			if (state->iss_reason[0] != '\0') {
603 				xo_emit("{V:state/%s}\n", state->iss_reason);
604 			} else {
605 				if (conf->isc_discovery) {
606 					xo_emit("{V:state}\n", "Discovery");
607 				} else if (conf->isc_enable == 0) {
608 					xo_emit("{V:state}\n", "Disabled");
609 				} else if (state->iss_connected) {
610 					xo_emit("{V:state}: ", "Connected");
611 					print_periphs(state->iss_id);
612 					xo_emit("\n");
613 				} else {
614 					xo_emit("{V:state}\n", "Disconnected");
615 				}
616 			}
617 			xo_close_instance("session");
618 		}
619 		if (isl.isl_nentries != 0)
620 			xo_close_list("session");
621 	}
622 
623 	return (0);
624 }
625 
626 static int
627 kernel_wait(int iscsi_fd, int timeout)
628 {
629 	struct iscsi_session_state *states = NULL;
630 	const struct iscsi_session_state *state;
631 	struct iscsi_session_list isl;
632 	unsigned int i, nentries = 1;
633 	bool all_connected;
634 	int error;
635 
636 	for (;;) {
637 		for (;;) {
638 			states = realloc(states,
639 			    nentries * sizeof(struct iscsi_session_state));
640 			if (states == NULL)
641 				xo_err(1, "realloc");
642 
643 			memset(&isl, 0, sizeof(isl));
644 			isl.isl_nentries = nentries;
645 			isl.isl_pstates = states;
646 
647 			error = ioctl(iscsi_fd, ISCSISLIST, &isl);
648 			if (error != 0 && errno == EMSGSIZE) {
649 				nentries *= 4;
650 				continue;
651 			}
652 			break;
653 		}
654 		if (error != 0) {
655 			xo_warn("ISCSISLIST");
656 			return (error);
657 		}
658 
659 		all_connected = true;
660 		for (i = 0; i < isl.isl_nentries; i++) {
661 			state = &states[i];
662 
663 			if (!state->iss_connected) {
664 				all_connected = false;
665 				break;
666 			}
667 		}
668 
669 		if (all_connected)
670 			return (0);
671 
672 		sleep(1);
673 
674 		if (timeout > 0) {
675 			timeout--;
676 			if (timeout == 0)
677 				return (1);
678 		}
679 	}
680 }
681 
682 static void
683 usage(void)
684 {
685 
686 	fprintf(stderr, "usage: iscsictl -A -p portal -t target "
687 	    "[-u user -s secret] [-w timeout] [-e on | off]\n");
688 	fprintf(stderr, "       iscsictl -A -d discovery-host "
689 	    "[-u user -s secret] [-e on | off]\n");
690 	fprintf(stderr, "       iscsictl -A -a [-c path]\n");
691 	fprintf(stderr, "       iscsictl -A -n nickname [-c path]\n");
692 	fprintf(stderr, "       iscsictl -M -i session-id [-p portal] "
693 	    "[-t target] [-u user] [-s secret] [-e on | off]\n");
694 	fprintf(stderr, "       iscsictl -M -i session-id -n nickname "
695 	    "[-c path]\n");
696 	fprintf(stderr, "       iscsictl -R [-p portal] [-t target]\n");
697 	fprintf(stderr, "       iscsictl -R -a\n");
698 	fprintf(stderr, "       iscsictl -R -n nickname [-c path]\n");
699 	fprintf(stderr, "       iscsictl -L [-v] [-w timeout]\n");
700 	exit(1);
701 }
702 
703 char *
704 checked_strdup(const char *s)
705 {
706 	char *c;
707 
708 	c = strdup(s);
709 	if (c == NULL)
710 		xo_err(1, "strdup");
711 	return (c);
712 }
713 
714 int
715 main(int argc, char **argv)
716 {
717 	int Aflag = 0, Mflag = 0, Rflag = 0, Lflag = 0, aflag = 0,
718 	    rflag = 0, vflag = 0;
719 	const char *conf_path = DEFAULT_CONFIG_PATH;
720 	char *nickname = NULL, *discovery_host = NULL, *portal = NULL,
721 	    *target = NULL, *user = NULL, *secret = NULL;
722 	int timeout = -1, enable = ENABLE_UNSPECIFIED;
723 	long long session_id = -1;
724 	char *end;
725 	int ch, error, iscsi_fd, retval, saved_errno;
726 	int failed = 0;
727 	struct conf *conf;
728 	struct target *targ;
729 
730 	argc = xo_parse_args(argc, argv);
731 	xo_open_container("iscsictl");
732 
733 	while ((ch = getopt(argc, argv, "AMRLac:d:e:i:n:p:rt:u:s:vw:")) != -1) {
734 		switch (ch) {
735 		case 'A':
736 			Aflag = 1;
737 			break;
738 		case 'M':
739 			Mflag = 1;
740 			break;
741 		case 'R':
742 			Rflag = 1;
743 			break;
744 		case 'L':
745 			Lflag = 1;
746 			break;
747 		case 'a':
748 			aflag = 1;
749 			break;
750 		case 'c':
751 			conf_path = optarg;
752 			break;
753 		case 'd':
754 			discovery_host = optarg;
755 			break;
756 		case 'e':
757 			enable = parse_enable(optarg);
758 			if (enable == ENABLE_UNSPECIFIED) {
759 				xo_errx(1, "invalid argument to -e, "
760 				    "must be either \"on\" or \"off\"");
761 			}
762 			break;
763 		case 'i':
764 			session_id = strtol(optarg, &end, 10);
765 			if ((size_t)(end - optarg) != strlen(optarg))
766 				xo_errx(1, "trailing characters after session-id");
767 			if (session_id < 0)
768 				xo_errx(1, "session-id cannot be negative");
769 			if (session_id > UINT_MAX)
770 				xo_errx(1, "session-id cannot be greater than %u",
771 				    UINT_MAX);
772 			break;
773 		case 'n':
774 			nickname = optarg;
775 			break;
776 		case 'p':
777 			portal = optarg;
778 			break;
779 		case 'r':
780 			rflag = 1;
781 			break;
782 		case 't':
783 			target = optarg;
784 			break;
785 		case 'u':
786 			user = optarg;
787 			break;
788 		case 's':
789 			secret = optarg;
790 			break;
791 		case 'v':
792 			vflag = 1;
793 			break;
794 		case 'w':
795 			timeout = strtol(optarg, &end, 10);
796 			if ((size_t)(end - optarg) != strlen(optarg))
797 				xo_errx(1, "trailing characters after timeout");
798 			if (timeout < 0)
799 				xo_errx(1, "timeout cannot be negative");
800 			break;
801 		case '?':
802 		default:
803 			usage();
804 		}
805 	}
806 	argc -= optind;
807 	if (argc != 0)
808 		usage();
809 
810 	if (Aflag + Mflag + Rflag + Lflag == 0)
811 		Lflag = 1;
812 	if (Aflag + Mflag + Rflag + Lflag > 1)
813 		xo_errx(1, "at most one of -A, -M, -R, or -L may be specified");
814 
815 	/*
816 	 * Note that we ignore unnecessary/inapplicable "-c" flag; so that
817 	 * people can do something like "alias ISCSICTL="iscsictl -c path"
818 	 * in shell scripts.
819 	 */
820 	if (Aflag != 0) {
821 		if (aflag != 0) {
822 			if (enable != ENABLE_UNSPECIFIED)
823 				xo_errx(1, "-a and -e and mutually exclusive");
824 			if (portal != NULL)
825 				xo_errx(1, "-a and -p and mutually exclusive");
826 			if (target != NULL)
827 				xo_errx(1, "-a and -t and mutually exclusive");
828 			if (user != NULL)
829 				xo_errx(1, "-a and -u and mutually exclusive");
830 			if (secret != NULL)
831 				xo_errx(1, "-a and -s and mutually exclusive");
832 			if (nickname != NULL)
833 				xo_errx(1, "-a and -n and mutually exclusive");
834 			if (discovery_host != NULL)
835 				xo_errx(1, "-a and -d and mutually exclusive");
836 			if (rflag != 0)
837 				xo_errx(1, "-a and -r and mutually exclusive");
838 		} else if (nickname != NULL) {
839 			if (enable != ENABLE_UNSPECIFIED)
840 				xo_errx(1, "-n and -e and mutually exclusive");
841 			if (portal != NULL)
842 				xo_errx(1, "-n and -p and mutually exclusive");
843 			if (target != NULL)
844 				xo_errx(1, "-n and -t and mutually exclusive");
845 			if (user != NULL)
846 				xo_errx(1, "-n and -u and mutually exclusive");
847 			if (secret != NULL)
848 				xo_errx(1, "-n and -s and mutually exclusive");
849 			if (discovery_host != NULL)
850 				xo_errx(1, "-n and -d and mutually exclusive");
851 			if (rflag != 0)
852 				xo_errx(1, "-n and -r and mutually exclusive");
853 		} else if (discovery_host != NULL) {
854 			if (portal != NULL)
855 				xo_errx(1, "-d and -p and mutually exclusive");
856 			if (target != NULL)
857 				xo_errx(1, "-d and -t and mutually exclusive");
858 		} else {
859 			if (target == NULL && portal == NULL)
860 				xo_errx(1, "must specify -a, -n or -t/-p");
861 
862 			if (target != NULL && portal == NULL)
863 				xo_errx(1, "-t must always be used with -p");
864 			if (portal != NULL && target == NULL)
865 				xo_errx(1, "-p must always be used with -t");
866 		}
867 
868 		if (user != NULL && secret == NULL)
869 			xo_errx(1, "-u must always be used with -s");
870 		if (secret != NULL && user == NULL)
871 			xo_errx(1, "-s must always be used with -u");
872 
873 		if (session_id != -1)
874 			xo_errx(1, "-i cannot be used with -A");
875 		if (vflag != 0)
876 			xo_errx(1, "-v cannot be used with -A");
877 
878 	} else if (Mflag != 0) {
879 		if (session_id == -1)
880 			xo_errx(1, "-M requires -i");
881 
882 		if (nickname != NULL) {
883 			if (enable != ENABLE_UNSPECIFIED)
884 				xo_errx(1, "-n and -e and mutually exclusive");
885 			if (portal != NULL)
886 				xo_errx(1, "-n and -p and mutually exclusive");
887 			if (target != NULL)
888 				xo_errx(1, "-n and -t and mutually exclusive");
889 			if (user != NULL)
890 				xo_errx(1, "-n and -u and mutually exclusive");
891 			if (secret != NULL)
892 				xo_errx(1, "-n and -s and mutually exclusive");
893 		}
894 
895 		if (aflag != 0)
896 			xo_errx(1, "-a cannot be used with -M");
897 		if (discovery_host != NULL)
898 			xo_errx(1, "-d cannot be used with -M");
899 		if (rflag != 0)
900 			xo_errx(1, "-r cannot be used with -M");
901 		if (vflag != 0)
902 			xo_errx(1, "-v cannot be used with -M");
903 		if (timeout != -1)
904 			xo_errx(1, "-w cannot be used with -M");
905 
906 	} else if (Rflag != 0) {
907 		if (aflag != 0) {
908 			if (portal != NULL)
909 				xo_errx(1, "-a and -p and mutually exclusive");
910 			if (target != NULL)
911 				xo_errx(1, "-a and -t and mutually exclusive");
912 			if (nickname != NULL)
913 				xo_errx(1, "-a and -n and mutually exclusive");
914 		} else if (nickname != NULL) {
915 			if (portal != NULL)
916 				xo_errx(1, "-n and -p and mutually exclusive");
917 			if (target != NULL)
918 				xo_errx(1, "-n and -t and mutually exclusive");
919 		} else if (target == NULL && portal == NULL) {
920 			xo_errx(1, "must specify either -a, -n, -t, or -p");
921 		}
922 
923 		if (discovery_host != NULL)
924 			xo_errx(1, "-d cannot be used with -R");
925 		if (enable != ENABLE_UNSPECIFIED)
926 			xo_errx(1, "-e cannot be used with -R");
927 		if (session_id != -1)
928 			xo_errx(1, "-i cannot be used with -R");
929 		if (rflag != 0)
930 			xo_errx(1, "-r cannot be used with -R");
931 		if (user != NULL)
932 			xo_errx(1, "-u cannot be used with -R");
933 		if (secret != NULL)
934 			xo_errx(1, "-s cannot be used with -R");
935 		if (vflag != 0)
936 			xo_errx(1, "-v cannot be used with -R");
937 		if (timeout != -1)
938 			xo_errx(1, "-w cannot be used with -R");
939 
940 	} else {
941 		assert(Lflag != 0);
942 
943 		if (discovery_host != NULL)
944 			xo_errx(1, "-d cannot be used with -L");
945 		if (session_id != -1)
946 			xo_errx(1, "-i cannot be used with -L");
947 		if (nickname != NULL)
948 			xo_errx(1, "-n cannot be used with -L");
949 		if (portal != NULL)
950 			xo_errx(1, "-p cannot be used with -L");
951 		if (rflag != 0)
952 			xo_errx(1, "-r cannot be used with -L");
953 		if (target != NULL)
954 			xo_errx(1, "-t cannot be used with -L");
955 		if (user != NULL)
956 			xo_errx(1, "-u cannot be used with -L");
957 		if (secret != NULL)
958 			xo_errx(1, "-s cannot be used with -L");
959 	}
960 
961 	iscsi_fd = open(ISCSI_PATH, O_RDWR);
962 	if (iscsi_fd < 0 && errno == ENOENT) {
963 		saved_errno = errno;
964 		retval = kldload("iscsi");
965 		if (retval != -1)
966 			iscsi_fd = open(ISCSI_PATH, O_RDWR);
967 		else
968 			errno = saved_errno;
969 	}
970 	if (iscsi_fd < 0)
971 		xo_err(1, "failed to open %s", ISCSI_PATH);
972 
973 	if (Aflag != 0 && aflag != 0) {
974 		conf = conf_new_from_file(conf_path);
975 
976 		TAILQ_FOREACH(targ, &conf->conf_targets, t_next)
977 			failed += kernel_add(iscsi_fd, targ);
978 	} else if (nickname != NULL) {
979 		conf = conf_new_from_file(conf_path);
980 		targ = target_find(conf, nickname);
981 		if (targ == NULL)
982 			xo_errx(1, "target %s not found in %s",
983 			    nickname, conf_path);
984 
985 		if (Aflag != 0)
986 			failed += kernel_add(iscsi_fd, targ);
987 		else if (Mflag != 0)
988 			failed += kernel_modify(iscsi_fd, session_id, targ);
989 		else if (Rflag != 0)
990 			failed += kernel_remove(iscsi_fd, targ);
991 		else
992 			failed += kernel_list(iscsi_fd, targ, vflag);
993 	} else if (Mflag != 0) {
994 		kernel_modify_some(iscsi_fd, session_id, target, portal,
995 		    user, secret, enable);
996 	} else {
997 		if (Aflag != 0 && target != NULL) {
998 			if (valid_iscsi_name(target) == false)
999 				xo_errx(1, "invalid target name \"%s\"", target);
1000 		}
1001 		conf = conf_new();
1002 		targ = target_new(conf);
1003 		targ->t_initiator_name = default_initiator_name();
1004 		targ->t_header_digest = DIGEST_NONE;
1005 		targ->t_data_digest = DIGEST_NONE;
1006 		targ->t_name = target;
1007 		if (discovery_host != NULL) {
1008 			targ->t_session_type = SESSION_TYPE_DISCOVERY;
1009 			targ->t_address = discovery_host;
1010 		} else {
1011 			targ->t_session_type = SESSION_TYPE_NORMAL;
1012 			targ->t_address = portal;
1013 		}
1014 		targ->t_enable = enable;
1015 		if (rflag != 0)
1016 			targ->t_protocol = PROTOCOL_ISER;
1017 		targ->t_user = user;
1018 		targ->t_secret = secret;
1019 
1020 		if (Aflag != 0)
1021 			failed += kernel_add(iscsi_fd, targ);
1022 		else if (Rflag != 0)
1023 			failed += kernel_remove(iscsi_fd, targ);
1024 		else
1025 			failed += kernel_list(iscsi_fd, targ, vflag);
1026 	}
1027 
1028 	if (timeout != -1)
1029 		failed += kernel_wait(iscsi_fd, timeout);
1030 
1031 	error = close(iscsi_fd);
1032 	if (error != 0)
1033 		xo_err(1, "close");
1034 
1035 	xo_close_container("iscsictl");
1036 	xo_finish();
1037 
1038 	if (failed != 0)
1039 		return (1);
1040 
1041 	return (0);
1042 }
1043