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 <err.h>
40#include <errno.h>
41#include <fcntl.h>
42#include <limits.h>
43#include <stdio.h>
44#include <stdlib.h>
45#include <string.h>
46#include <unistd.h>
47
48#include <iscsi_ioctl.h>
49#include "iscsictl.h"
50
51struct conf *
52conf_new(void)
53{
54	struct conf *conf;
55
56	conf = calloc(1, sizeof(*conf));
57	if (conf == NULL)
58		err(1, "calloc");
59
60	TAILQ_INIT(&conf->conf_targets);
61
62	return (conf);
63}
64
65struct target *
66target_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
79struct target *
80target_new(struct conf *conf)
81{
82	struct target *targ;
83
84	targ = calloc(1, sizeof(*targ));
85	if (targ == NULL)
86		err(1, "calloc");
87	targ->t_conf = conf;
88	TAILQ_INSERT_TAIL(&conf->conf_targets, targ, t_next);
89
90	return (targ);
91}
92
93void
94target_delete(struct target *targ)
95{
96
97	TAILQ_REMOVE(&targ->t_conf->conf_targets, targ, t_next);
98	free(targ);
99}
100
101
102static char *
103default_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		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		err(1, "gethostname");
119
120	return (name);
121}
122
123static bool
124valid_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
155bool
156valid_iscsi_name(const char *name)
157{
158	int i;
159
160	if (strlen(name) >= MAX_NAME_LEN) {
161		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			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			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				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			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				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		warnx("invalid iSCSI name \"%s\"; should start with "
217		    "either \".iqn\", \"eui.\", or \"naa.\"",
218		    name);
219	}
220	return (true);
221}
222
223void
224conf_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			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			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				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			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			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				errx(1, "missing chapIName for target \"%s\"",
272				    targ->t_nickname);
273			}
274			if (targ->t_secret == NULL)
275				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					errx(1, "missing tgtChapName for "
281					    "target \"%s\"", targ->t_nickname);
282				if (targ->t_mutual_secret == NULL)
283					errx(1, "missing tgtChapSecret for "
284					    "target \"%s\"", targ->t_nickname);
285			}
286		}
287	}
288}
289
290static void
291conf_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_header_digest == DIGEST_CRC32C)
331		conf->isc_header_digest = ISCSI_DIGEST_CRC32C;
332	else
333		conf->isc_header_digest = ISCSI_DIGEST_NONE;
334	if (targ->t_data_digest == DIGEST_CRC32C)
335		conf->isc_data_digest = ISCSI_DIGEST_CRC32C;
336	else
337		conf->isc_data_digest = ISCSI_DIGEST_NONE;
338}
339
340static int
341kernel_add(int iscsi_fd, const struct target *targ)
342{
343	struct iscsi_session_add isa;
344	int error;
345
346	memset(&isa, 0, sizeof(isa));
347	conf_from_target(&isa.isa_conf, targ);
348	error = ioctl(iscsi_fd, ISCSISADD, &isa);
349	if (error != 0)
350		warn("ISCSISADD");
351	return (error);
352}
353
354static int
355kernel_modify(int iscsi_fd, unsigned int session_id, const struct target *targ)
356{
357	struct iscsi_session_modify ism;
358	int error;
359
360	memset(&ism, 0, sizeof(ism));
361	ism.ism_session_id = session_id;
362	conf_from_target(&ism.ism_conf, targ);
363	error = ioctl(iscsi_fd, ISCSISMODIFY, &ism);
364	if (error != 0)
365		warn("ISCSISMODIFY");
366	return (error);
367}
368
369static void
370kernel_modify_some(int iscsi_fd, unsigned int session_id, const char *target,
371  const char *target_addr, const char *user, const char *secret)
372{
373	struct iscsi_session_state *states = NULL;
374	struct iscsi_session_state *state;
375	struct iscsi_session_conf *conf;
376	struct iscsi_session_list isl;
377	struct iscsi_session_modify ism;
378	unsigned int i, nentries = 1;
379	int error;
380
381	for (;;) {
382		states = realloc(states,
383		    nentries * sizeof(struct iscsi_session_state));
384		if (states == NULL)
385			err(1, "realloc");
386
387		memset(&isl, 0, sizeof(isl));
388		isl.isl_nentries = nentries;
389		isl.isl_pstates = states;
390
391		error = ioctl(iscsi_fd, ISCSISLIST, &isl);
392		if (error != 0 && errno == EMSGSIZE) {
393			nentries *= 4;
394			continue;
395		}
396		break;
397	}
398	if (error != 0)
399		errx(1, "ISCSISLIST");
400
401	for (i = 0; i < isl.isl_nentries; i++) {
402		state = &states[i];
403
404		if (state->iss_id == session_id)
405			break;
406	}
407	if (i == isl.isl_nentries)
408		errx(1, "session-id %u not found", session_id);
409
410	conf = &state->iss_conf;
411
412	if (target != NULL)
413		strlcpy(conf->isc_target, target, sizeof(conf->isc_target));
414	if (target_addr != NULL)
415		strlcpy(conf->isc_target_addr, target_addr,
416		    sizeof(conf->isc_target_addr));
417	if (user != NULL)
418		strlcpy(conf->isc_user, user, sizeof(conf->isc_user));
419	if (secret != NULL)
420		strlcpy(conf->isc_secret, secret, sizeof(conf->isc_secret));
421
422	memset(&ism, 0, sizeof(ism));
423	ism.ism_session_id = session_id;
424	memcpy(&ism.ism_conf, conf, sizeof(ism.ism_conf));
425	error = ioctl(iscsi_fd, ISCSISMODIFY, &ism);
426	if (error != 0)
427		warn("ISCSISMODIFY");
428}
429
430static int
431kernel_remove(int iscsi_fd, const struct target *targ)
432{
433	struct iscsi_session_remove isr;
434	int error;
435
436	memset(&isr, 0, sizeof(isr));
437	conf_from_target(&isr.isr_conf, targ);
438	error = ioctl(iscsi_fd, ISCSISREMOVE, &isr);
439	if (error != 0)
440		warn("ISCSISREMOVE");
441	return (error);
442}
443
444/*
445 * XXX: Add filtering.
446 */
447static int
448kernel_list(int iscsi_fd, const struct target *targ __unused,
449    int verbose)
450{
451	struct iscsi_session_state *states = NULL;
452	const struct iscsi_session_state *state;
453	const struct iscsi_session_conf *conf;
454	struct iscsi_session_list isl;
455	unsigned int i, nentries = 1;
456	int error;
457
458	for (;;) {
459		states = realloc(states,
460		    nentries * sizeof(struct iscsi_session_state));
461		if (states == NULL)
462			err(1, "realloc");
463
464		memset(&isl, 0, sizeof(isl));
465		isl.isl_nentries = nentries;
466		isl.isl_pstates = states;
467
468		error = ioctl(iscsi_fd, ISCSISLIST, &isl);
469		if (error != 0 && errno == EMSGSIZE) {
470			nentries *= 4;
471			continue;
472		}
473		break;
474	}
475	if (error != 0) {
476		warn("ISCSISLIST");
477		return (error);
478	}
479
480	if (verbose != 0) {
481		for (i = 0; i < isl.isl_nentries; i++) {
482			state = &states[i];
483			conf = &state->iss_conf;
484
485			printf("Session ID:       %u\n", state->iss_id);
486			printf("Initiator name:   %s\n", conf->isc_initiator);
487			printf("Initiator portal: %s\n",
488			    conf->isc_initiator_addr);
489			printf("Initiator alias:  %s\n",
490			    conf->isc_initiator_alias);
491			printf("Target name:      %s\n", conf->isc_target);
492			printf("Target portal:    %s\n",
493			    conf->isc_target_addr);
494			printf("Target alias:     %s\n",
495			    state->iss_target_alias);
496			printf("User:             %s\n", conf->isc_user);
497			printf("Secret:           %s\n", conf->isc_secret);
498			printf("Mutual user:      %s\n",
499			    conf->isc_mutual_user);
500			printf("Mutual secret:    %s\n",
501			    conf->isc_mutual_secret);
502			printf("Session type:     %s\n",
503			    conf->isc_discovery ? "Discovery" : "Normal");
504			printf("Session state:    %s\n",
505			    state->iss_connected ?
506			    "Connected" : "Disconnected");
507			printf("Failure reason:   %s\n", state->iss_reason);
508			printf("Header digest:    %s\n",
509			    state->iss_header_digest == ISCSI_DIGEST_CRC32C ?
510			    "CRC32C" : "None");
511			printf("Data digest:      %s\n",
512			    state->iss_data_digest == ISCSI_DIGEST_CRC32C ?
513			    "CRC32C" : "None");
514			printf("DataSegmentLen:   %d\n",
515			    state->iss_max_data_segment_length);
516			printf("ImmediateData:    %s\n",
517			    state->iss_immediate_data ? "Yes" : "No");
518			printf("iSER (RDMA):      %s\n",
519			    conf->isc_iser ? "Yes" : "No");
520			printf("Device nodes:     ");
521			print_periphs(state->iss_id);
522			printf("\n\n");
523		}
524	} else {
525		printf("%-36s %-16s %s\n",
526		    "Target name", "Target portal", "State");
527		for (i = 0; i < isl.isl_nentries; i++) {
528			state = &states[i];
529			conf = &state->iss_conf;
530
531			printf("%-36s %-16s ",
532			    conf->isc_target, conf->isc_target_addr);
533
534			if (state->iss_reason[0] != '\0') {
535				printf("%s\n", state->iss_reason);
536			} else {
537				if (conf->isc_discovery) {
538					printf("Discovery\n");
539				} else if (state->iss_connected) {
540					printf("Connected: ");
541					print_periphs(state->iss_id);
542					printf("\n");
543				} else {
544					printf("Disconnected\n");
545				}
546			}
547		}
548	}
549
550	return (0);
551}
552
553static int
554kernel_wait(int iscsi_fd, int timeout)
555{
556	struct iscsi_session_state *states = NULL;
557	const struct iscsi_session_state *state;
558	const struct iscsi_session_conf *conf;
559	struct iscsi_session_list isl;
560	unsigned int i, nentries = 1;
561	bool all_connected;
562	int error;
563
564	for (;;) {
565		for (;;) {
566			states = realloc(states,
567			    nentries * sizeof(struct iscsi_session_state));
568			if (states == NULL)
569				err(1, "realloc");
570
571			memset(&isl, 0, sizeof(isl));
572			isl.isl_nentries = nentries;
573			isl.isl_pstates = states;
574
575			error = ioctl(iscsi_fd, ISCSISLIST, &isl);
576			if (error != 0 && errno == EMSGSIZE) {
577				nentries *= 4;
578				continue;
579			}
580			break;
581		}
582		if (error != 0) {
583			warn("ISCSISLIST");
584			return (error);
585		}
586
587		all_connected = true;
588		for (i = 0; i < isl.isl_nentries; i++) {
589			state = &states[i];
590			conf = &state->iss_conf;
591
592			if (!state->iss_connected) {
593				all_connected = false;
594				break;
595			}
596		}
597
598		if (all_connected)
599			return (0);
600
601		sleep(1);
602
603		if (timeout > 0) {
604			timeout--;
605			if (timeout == 0)
606				return (1);
607		}
608	}
609}
610
611static void
612usage(void)
613{
614
615	fprintf(stderr, "usage: iscsictl -A -p portal -t target "
616	    "[-u user -s secret] [-w timeout]\n");
617	fprintf(stderr, "       iscsictl -A -d discovery-host "
618	    "[-u user -s secret]\n");
619	fprintf(stderr, "       iscsictl -A -a [-c path]\n");
620	fprintf(stderr, "       iscsictl -A -n nickname [-c path]\n");
621	fprintf(stderr, "       iscsictl -M -i session-id [-p portal] "
622	    "[-t target] [-u user] [-s secret]\n");
623	fprintf(stderr, "       iscsictl -M -i session-id -n nickname "
624	    "[-c path]\n");
625	fprintf(stderr, "       iscsictl -R [-p portal] [-t target]\n");
626	fprintf(stderr, "       iscsictl -R -a\n");
627	fprintf(stderr, "       iscsictl -R -n nickname [-c path]\n");
628	fprintf(stderr, "       iscsictl -L [-v] [-w timeout]\n");
629	exit(1);
630}
631
632char *
633checked_strdup(const char *s)
634{
635	char *c;
636
637	c = strdup(s);
638	if (c == NULL)
639		err(1, "strdup");
640	return (c);
641}
642
643int
644main(int argc, char **argv)
645{
646	int Aflag = 0, Mflag = 0, Rflag = 0, Lflag = 0, aflag = 0, vflag = 0;
647	const char *conf_path = DEFAULT_CONFIG_PATH;
648	char *nickname = NULL, *discovery_host = NULL, *portal = NULL,
649	     *target = NULL, *user = NULL, *secret = NULL;
650	int timeout = -1;
651	long long session_id = -1;
652	char *end;
653	int ch, error, iscsi_fd, retval, saved_errno;
654	int failed = 0;
655	struct conf *conf;
656	struct target *targ;
657
658	while ((ch = getopt(argc, argv, "AMRLac:d:i:n:p:t:u:s:vw:")) != -1) {
659		switch (ch) {
660		case 'A':
661			Aflag = 1;
662			break;
663		case 'M':
664			Mflag = 1;
665			break;
666		case 'R':
667			Rflag = 1;
668			break;
669		case 'L':
670			Lflag = 1;
671			break;
672		case 'a':
673			aflag = 1;
674			break;
675		case 'c':
676			conf_path = optarg;
677			break;
678		case 'd':
679			discovery_host = optarg;
680			break;
681		case 'i':
682			session_id = strtol(optarg, &end, 10);
683			if ((size_t)(end - optarg) != strlen(optarg))
684				errx(1, "trailing characters after session-id");
685			if (session_id < 0)
686				errx(1, "session-id cannot be negative");
687			if (session_id > UINT_MAX)
688				errx(1, "session-id cannot be greater than %u",
689				    UINT_MAX);
690			break;
691		case 'n':
692			nickname = optarg;
693			break;
694		case 'p':
695			portal = optarg;
696			break;
697		case 't':
698			target = optarg;
699			break;
700		case 'u':
701			user = optarg;
702			break;
703		case 's':
704			secret = optarg;
705			break;
706		case 'v':
707			vflag = 1;
708			break;
709		case 'w':
710			timeout = strtol(optarg, &end, 10);
711			if ((size_t)(end - optarg) != strlen(optarg))
712				errx(1, "trailing characters after timeout");
713			if (timeout < 0)
714				errx(1, "timeout cannot be negative");
715			break;
716		case '?':
717		default:
718			usage();
719		}
720	}
721	argc -= optind;
722	if (argc != 0)
723		usage();
724
725	if (Aflag + Mflag + Rflag + Lflag == 0)
726		Lflag = 1;
727	if (Aflag + Mflag + Rflag + Lflag > 1)
728		errx(1, "at most one of -A, -M, -R, or -L may be specified");
729
730	/*
731	 * Note that we ignore unneccessary/inapplicable "-c" flag; so that
732	 * people can do something like "alias ISCSICTL="iscsictl -c path"
733	 * in shell scripts.
734	 */
735	if (Aflag != 0) {
736		if (aflag != 0) {
737			if (portal != NULL)
738				errx(1, "-a and -p and mutually exclusive");
739			if (target != NULL)
740				errx(1, "-a and -t and mutually exclusive");
741			if (user != NULL)
742				errx(1, "-a and -u and mutually exclusive");
743			if (secret != NULL)
744				errx(1, "-a and -s and mutually exclusive");
745			if (nickname != NULL)
746				errx(1, "-a and -n and mutually exclusive");
747			if (discovery_host != NULL)
748				errx(1, "-a and -d and mutually exclusive");
749		} else if (nickname != NULL) {
750			if (portal != NULL)
751				errx(1, "-n and -p and mutually exclusive");
752			if (target != NULL)
753				errx(1, "-n and -t and mutually exclusive");
754			if (user != NULL)
755				errx(1, "-n and -u and mutually exclusive");
756			if (secret != NULL)
757				errx(1, "-n and -s and mutually exclusive");
758			if (discovery_host != NULL)
759				errx(1, "-n and -d and mutually exclusive");
760		} else if (discovery_host != NULL) {
761			if (portal != NULL)
762				errx(1, "-d and -p and mutually exclusive");
763			if (target != NULL)
764				errx(1, "-d and -t and mutually exclusive");
765		} else {
766			if (target == NULL && portal == NULL)
767				errx(1, "must specify -a, -n or -t/-p");
768
769			if (target != NULL && portal == NULL)
770				errx(1, "-t must always be used with -p");
771			if (portal != NULL && target == NULL)
772				errx(1, "-p must always be used with -t");
773		}
774
775		if (user != NULL && secret == NULL)
776			errx(1, "-u must always be used with -s");
777		if (secret != NULL && user == NULL)
778			errx(1, "-s must always be used with -u");
779
780		if (session_id != -1)
781			errx(1, "-i cannot be used with -A");
782		if (vflag != 0)
783			errx(1, "-v cannot be used with -A");
784
785	} else if (Mflag != 0) {
786		if (session_id == -1)
787			errx(1, "-M requires -i");
788
789		if (discovery_host != NULL)
790			errx(1, "-M and -d are mutually exclusive");
791		if (aflag != 0)
792			errx(1, "-M and -a are mutually exclusive");
793		if (nickname != NULL) {
794			if (portal != NULL)
795				errx(1, "-n and -p and mutually exclusive");
796			if (target != NULL)
797				errx(1, "-n and -t and mutually exclusive");
798			if (user != NULL)
799				errx(1, "-n and -u and mutually exclusive");
800			if (secret != NULL)
801				errx(1, "-n and -s and mutually exclusive");
802		}
803
804		if (vflag != 0)
805			errx(1, "-v cannot be used with -M");
806		if (timeout != -1)
807			errx(1, "-w cannot be used with -M");
808
809	} else if (Rflag != 0) {
810		if (user != NULL)
811			errx(1, "-R and -u are mutually exclusive");
812		if (secret != NULL)
813			errx(1, "-R and -s are mutually exclusive");
814		if (discovery_host != NULL)
815			errx(1, "-R and -d are mutually exclusive");
816
817		if (aflag != 0) {
818			if (portal != NULL)
819				errx(1, "-a and -p and mutually exclusive");
820			if (target != NULL)
821				errx(1, "-a and -t and mutually exclusive");
822			if (nickname != NULL)
823				errx(1, "-a and -n and mutually exclusive");
824		} else if (nickname != NULL) {
825			if (portal != NULL)
826				errx(1, "-n and -p and mutually exclusive");
827			if (target != NULL)
828				errx(1, "-n and -t and mutually exclusive");
829		} else if (target == NULL && portal == NULL) {
830			errx(1, "must specify either -a, -n, -t, or -p");
831		}
832
833		if (session_id != -1)
834			errx(1, "-i cannot be used with -R");
835		if (vflag != 0)
836			errx(1, "-v cannot be used with -R");
837		if (timeout != -1)
838			errx(1, "-w cannot be used with -R");
839
840	} else {
841		assert(Lflag != 0);
842
843		if (portal != NULL)
844			errx(1, "-L and -p and mutually exclusive");
845		if (target != NULL)
846			errx(1, "-L and -t and mutually exclusive");
847		if (user != NULL)
848			errx(1, "-L and -u and mutually exclusive");
849		if (secret != NULL)
850			errx(1, "-L and -s and mutually exclusive");
851		if (nickname != NULL)
852			errx(1, "-L and -n and mutually exclusive");
853		if (discovery_host != NULL)
854			errx(1, "-L and -d and mutually exclusive");
855
856		if (session_id != -1)
857			errx(1, "-i cannot be used with -L");
858	}
859
860	iscsi_fd = open(ISCSI_PATH, O_RDWR);
861	if (iscsi_fd < 0 && errno == ENOENT) {
862		saved_errno = errno;
863		retval = kldload("iscsi");
864		if (retval != -1)
865			iscsi_fd = open(ISCSI_PATH, O_RDWR);
866		else
867			errno = saved_errno;
868	}
869	if (iscsi_fd < 0)
870		err(1, "failed to open %s", ISCSI_PATH);
871
872	if (Aflag != 0 && aflag != 0) {
873		conf = conf_new_from_file(conf_path);
874
875		TAILQ_FOREACH(targ, &conf->conf_targets, t_next)
876			failed += kernel_add(iscsi_fd, targ);
877	} else if (nickname != NULL) {
878		conf = conf_new_from_file(conf_path);
879		targ = target_find(conf, nickname);
880		if (targ == NULL)
881			errx(1, "target %s not found in %s",
882			    nickname, conf_path);
883
884		if (Aflag != 0)
885			failed += kernel_add(iscsi_fd, targ);
886		else if (Mflag != 0)
887			failed += kernel_modify(iscsi_fd, session_id, targ);
888		else if (Rflag != 0)
889			failed += kernel_remove(iscsi_fd, targ);
890		else
891			failed += kernel_list(iscsi_fd, targ, vflag);
892	} else if (Mflag != 0) {
893		kernel_modify_some(iscsi_fd, session_id, target, portal,
894		    user, secret);
895	} else {
896		if (Aflag != 0 && target != NULL) {
897			if (valid_iscsi_name(target) == false)
898				errx(1, "invalid target name \"%s\"", target);
899		}
900		conf = conf_new();
901		targ = target_new(conf);
902		targ->t_initiator_name = default_initiator_name();
903		targ->t_header_digest = DIGEST_NONE;
904		targ->t_data_digest = DIGEST_NONE;
905		targ->t_name = target;
906		if (discovery_host != NULL) {
907			targ->t_session_type = SESSION_TYPE_DISCOVERY;
908			targ->t_address = discovery_host;
909		} else {
910			targ->t_session_type = SESSION_TYPE_NORMAL;
911			targ->t_address = portal;
912		}
913		targ->t_user = user;
914		targ->t_secret = secret;
915
916		if (Aflag != 0)
917			failed += kernel_add(iscsi_fd, targ);
918		else if (Rflag != 0)
919			failed += kernel_remove(iscsi_fd, targ);
920		else
921			failed += kernel_list(iscsi_fd, targ, vflag);
922	}
923
924	if (timeout != -1)
925		failed += kernel_wait(iscsi_fd, timeout);
926
927	error = close(iscsi_fd);
928	if (error != 0)
929		err(1, "close");
930
931	if (failed > 0)
932		return (1);
933	return (0);
934}
935