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