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