1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2012 The FreeBSD Foundation
5 * All rights reserved.
6 *
7 * This software was developed by Edward Tomasz Napierala under sponsorship
8 * from the FreeBSD Foundation.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 *
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD$");
35
36#include <sys/ioctl.h>
37#include <sys/param.h>
38#include <sys/linker.h>
39#include <assert.h>
40#include <ctype.h>
41#include <errno.h>
42#include <fcntl.h>
43#include <limits.h>
44#include <stdio.h>
45#include <stdlib.h>
46#include <string.h>
47#include <unistd.h>
48#include <libxo/xo.h>
49
50#include <iscsi_ioctl.h>
51#include "iscsictl.h"
52
53struct conf *
54conf_new(void)
55{
56	struct conf *conf;
57
58	conf = calloc(1, sizeof(*conf));
59	if (conf == NULL)
60		xo_err(1, "calloc");
61
62	TAILQ_INIT(&conf->conf_targets);
63
64	return (conf);
65}
66
67struct target *
68target_find(struct conf *conf, const char *nickname)
69{
70	struct target *targ;
71
72	TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
73		if (targ->t_nickname != NULL &&
74		    strcasecmp(targ->t_nickname, nickname) == 0)
75			return (targ);
76	}
77
78	return (NULL);
79}
80
81struct target *
82target_new(struct conf *conf)
83{
84	struct target *targ;
85
86	targ = calloc(1, sizeof(*targ));
87	if (targ == NULL)
88		xo_err(1, "calloc");
89	targ->t_conf = conf;
90	targ->t_dscp = -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}
364
365static int
366kernel_add(int iscsi_fd, const struct target *targ)
367{
368	struct iscsi_session_add isa;
369	int error;
370
371	memset(&isa, 0, sizeof(isa));
372	conf_from_target(&isa.isa_conf, targ);
373	error = ioctl(iscsi_fd, ISCSISADD, &isa);
374	if (error != 0)
375		xo_warn("ISCSISADD");
376	return (error);
377}
378
379static int
380kernel_modify(int iscsi_fd, unsigned int session_id, const struct target *targ)
381{
382	struct iscsi_session_modify ism;
383	int error;
384
385	memset(&ism, 0, sizeof(ism));
386	ism.ism_session_id = session_id;
387	conf_from_target(&ism.ism_conf, targ);
388	error = ioctl(iscsi_fd, ISCSISMODIFY, &ism);
389	if (error != 0)
390		xo_warn("ISCSISMODIFY");
391	return (error);
392}
393
394static void
395kernel_modify_some(int iscsi_fd, unsigned int session_id, const char *target,
396  const char *target_addr, const char *user, const char *secret, int enable)
397{
398	struct iscsi_session_state *states = NULL;
399	struct iscsi_session_state *state;
400	struct iscsi_session_conf *conf;
401	struct iscsi_session_list isl;
402	struct iscsi_session_modify ism;
403	unsigned int i, nentries = 1;
404	int error;
405
406	for (;;) {
407		states = realloc(states,
408		    nentries * sizeof(struct iscsi_session_state));
409		if (states == NULL)
410			xo_err(1, "realloc");
411
412		memset(&isl, 0, sizeof(isl));
413		isl.isl_nentries = nentries;
414		isl.isl_pstates = states;
415
416		error = ioctl(iscsi_fd, ISCSISLIST, &isl);
417		if (error != 0 && errno == EMSGSIZE) {
418			nentries *= 4;
419			continue;
420		}
421		break;
422	}
423	if (error != 0)
424		xo_errx(1, "ISCSISLIST");
425
426	for (i = 0; i < isl.isl_nentries; i++) {
427		state = &states[i];
428
429		if (state->iss_id == session_id)
430			break;
431	}
432	if (i == isl.isl_nentries)
433		xo_errx(1, "session-id %u not found", session_id);
434
435	conf = &state->iss_conf;
436
437	if (target != NULL)
438		strlcpy(conf->isc_target, target, sizeof(conf->isc_target));
439	if (target_addr != NULL)
440		strlcpy(conf->isc_target_addr, target_addr,
441		    sizeof(conf->isc_target_addr));
442	if (user != NULL)
443		strlcpy(conf->isc_user, user, sizeof(conf->isc_user));
444	if (secret != NULL)
445		strlcpy(conf->isc_secret, secret, sizeof(conf->isc_secret));
446	if (enable == ENABLE_ON)
447		conf->isc_enable = 1;
448	else if (enable == ENABLE_OFF)
449		conf->isc_enable = 0;
450
451	memset(&ism, 0, sizeof(ism));
452	ism.ism_session_id = session_id;
453	memcpy(&ism.ism_conf, conf, sizeof(ism.ism_conf));
454	error = ioctl(iscsi_fd, ISCSISMODIFY, &ism);
455	if (error != 0)
456		xo_warn("ISCSISMODIFY");
457}
458
459static int
460kernel_remove(int iscsi_fd, const struct target *targ)
461{
462	struct iscsi_session_remove isr;
463	int error;
464
465	memset(&isr, 0, sizeof(isr));
466	conf_from_target(&isr.isr_conf, targ);
467	error = ioctl(iscsi_fd, ISCSISREMOVE, &isr);
468	if (error != 0)
469		xo_warn("ISCSISREMOVE");
470	return (error);
471}
472
473/*
474 * XXX: Add filtering.
475 */
476static int
477kernel_list(int iscsi_fd, const struct target *targ __unused,
478    int verbose)
479{
480	struct iscsi_session_state *states = NULL;
481	const struct iscsi_session_state *state;
482	const struct iscsi_session_conf *conf;
483	struct iscsi_session_list isl;
484	unsigned int i, nentries = 1;
485	int error;
486
487	for (;;) {
488		states = realloc(states,
489		    nentries * sizeof(struct iscsi_session_state));
490		if (states == NULL)
491			xo_err(1, "realloc");
492
493		memset(&isl, 0, sizeof(isl));
494		isl.isl_nentries = nentries;
495		isl.isl_pstates = states;
496
497		error = ioctl(iscsi_fd, ISCSISLIST, &isl);
498		if (error != 0 && errno == EMSGSIZE) {
499			nentries *= 4;
500			continue;
501		}
502		break;
503	}
504	if (error != 0) {
505		xo_warn("ISCSISLIST");
506		return (error);
507	}
508
509	if (verbose != 0) {
510		xo_open_list("session");
511		for (i = 0; i < isl.isl_nentries; i++) {
512			state = &states[i];
513			conf = &state->iss_conf;
514
515			xo_open_instance("session");
516
517			/*
518			 * Display-only modifier as this information
519			 * is also present within the 'session' container
520			 */
521			xo_emit("{L:/%-26s}{V:sessionId/%u}\n",
522			    "Session ID:", state->iss_id);
523
524			xo_open_container("initiator");
525			xo_emit("{L:/%-26s}{V:name/%s}\n",
526			    "Initiator name:", conf->isc_initiator);
527			xo_emit("{L:/%-26s}{V:portal/%s}\n",
528			    "Initiator portal:", conf->isc_initiator_addr);
529			xo_emit("{L:/%-26s}{V:alias/%s}\n",
530			    "Initiator alias:", conf->isc_initiator_alias);
531			xo_close_container("initiator");
532
533			xo_open_container("target");
534			xo_emit("{L:/%-26s}{V:name/%s}\n",
535			    "Target name:", conf->isc_target);
536			xo_emit("{L:/%-26s}{V:portal/%s}\n",
537			    "Target portal:", conf->isc_target_addr);
538			xo_emit("{L:/%-26s}{V:alias/%s}\n",
539			    "Target alias:", state->iss_target_alias);
540			if (conf->isc_dscp != -1)
541				xo_emit("{L:/%-26s}{V:dscp/0x%02x}\n",
542				    "Target DSCP:", conf->isc_dscp);
543			xo_close_container("target");
544
545			xo_open_container("auth");
546			xo_emit("{L:/%-26s}{V:user/%s}\n",
547			    "User:", conf->isc_user);
548			xo_emit("{L:/%-26s}{V:secret/%s}\n",
549			    "Secret:", conf->isc_secret);
550			xo_emit("{L:/%-26s}{V:mutualUser/%s}\n",
551			    "Mutual user:", conf->isc_mutual_user);
552			xo_emit("{L:/%-26s}{V:mutualSecret/%s}\n",
553			    "Mutual secret:", conf->isc_mutual_secret);
554			xo_close_container("auth");
555
556			xo_emit("{L:/%-26s}{V:type/%s}\n",
557			    "Session type:",
558			    conf->isc_discovery ? "Discovery" : "Normal");
559			xo_emit("{L:/%-26s}{V:enable/%s}\n",
560			    "Enable:",
561			    conf->isc_enable ? "Yes" : "No");
562			xo_emit("{L:/%-26s}{V:state/%s}\n",
563			    "Session state:",
564			    state->iss_connected ? "Connected" : "Disconnected");
565			xo_emit("{L:/%-26s}{V:failureReason/%s}\n",
566			    "Failure reason:", state->iss_reason);
567			xo_emit("{L:/%-26s}{V:headerDigest/%s}\n",
568			    "Header digest:",
569			    state->iss_header_digest == ISCSI_DIGEST_CRC32C ?
570			    "CRC32C" : "None");
571			xo_emit("{L:/%-26s}{V:dataDigest/%s}\n",
572			    "Data digest:",
573			    state->iss_data_digest == ISCSI_DIGEST_CRC32C ?
574			    "CRC32C" : "None");
575			xo_emit("{L:/%-26s}{V:recvDataSegmentLen/%d}\n",
576			    "MaxRecvDataSegmentLength:",
577			    state->iss_max_recv_data_segment_length);
578			xo_emit("{L:/%-26s}{V:sendDataSegmentLen/%d}\n",
579			    "MaxSendDataSegmentLength:",
580			    state->iss_max_send_data_segment_length);
581			xo_emit("{L:/%-26s}{V:maxBurstLen/%d}\n",
582			    "MaxBurstLen:", state->iss_max_burst_length);
583			xo_emit("{L:/%-26s}{V:firstBurstLen/%d}\n",
584			    "FirstBurstLen:", state->iss_first_burst_length);
585			xo_emit("{L:/%-26s}{V:immediateData/%s}\n",
586			    "ImmediateData:", state->iss_immediate_data ? "Yes" : "No");
587			xo_emit("{L:/%-26s}{V:iSER/%s}\n",
588			    "iSER (RDMA):", conf->isc_iser ? "Yes" : "No");
589			xo_emit("{L:/%-26s}{V:offloadDriver/%s}\n",
590			    "Offload driver:", state->iss_offload);
591			xo_emit("{L:/%-26s}",
592			    "Device nodes:");
593			print_periphs(state->iss_id);
594			xo_emit("\n\n");
595			xo_close_instance("session");
596		}
597		xo_close_list("session");
598	} else {
599		xo_emit("{T:/%-36s} {T:/%-16s} {T:/%s}\n",
600		    "Target name", "Target portal", "State");
601
602		if (isl.isl_nentries != 0)
603			xo_open_list("session");
604		for (i = 0; i < isl.isl_nentries; i++) {
605
606			state = &states[i];
607			conf = &state->iss_conf;
608
609			xo_open_instance("session");
610			xo_emit("{V:name/%-36s/%s} {V:portal/%-16s/%s} ",
611			    conf->isc_target, conf->isc_target_addr);
612
613			if (state->iss_reason[0] != '\0' &&
614			    conf->isc_enable != 0) {
615				xo_emit("{V:state/%s}\n", state->iss_reason);
616			} else {
617				if (conf->isc_discovery) {
618					xo_emit("{V:state}\n", "Discovery");
619				} else if (conf->isc_enable == 0) {
620					xo_emit("{V:state}\n", "Disabled");
621				} else if (state->iss_connected) {
622					xo_emit("{V:state}: ", "Connected");
623					print_periphs(state->iss_id);
624					xo_emit("\n");
625				} else {
626					xo_emit("{V:state}\n", "Disconnected");
627				}
628			}
629			xo_close_instance("session");
630		}
631		if (isl.isl_nentries != 0)
632			xo_close_list("session");
633	}
634
635	return (0);
636}
637
638static int
639kernel_wait(int iscsi_fd, int timeout)
640{
641	struct iscsi_session_state *states = NULL;
642	const struct iscsi_session_state *state;
643	struct iscsi_session_list isl;
644	unsigned int i, nentries = 1;
645	bool all_connected;
646	int error;
647
648	for (;;) {
649		for (;;) {
650			states = realloc(states,
651			    nentries * sizeof(struct iscsi_session_state));
652			if (states == NULL)
653				xo_err(1, "realloc");
654
655			memset(&isl, 0, sizeof(isl));
656			isl.isl_nentries = nentries;
657			isl.isl_pstates = states;
658
659			error = ioctl(iscsi_fd, ISCSISLIST, &isl);
660			if (error != 0 && errno == EMSGSIZE) {
661				nentries *= 4;
662				continue;
663			}
664			break;
665		}
666		if (error != 0) {
667			xo_warn("ISCSISLIST");
668			return (error);
669		}
670
671		all_connected = true;
672		for (i = 0; i < isl.isl_nentries; i++) {
673			state = &states[i];
674
675			if (!state->iss_connected) {
676				all_connected = false;
677				break;
678			}
679		}
680
681		if (all_connected)
682			return (0);
683
684		sleep(1);
685
686		if (timeout > 0) {
687			timeout--;
688			if (timeout == 0)
689				return (1);
690		}
691	}
692}
693
694static void
695usage(void)
696{
697
698	fprintf(stderr, "usage: iscsictl -A -p portal -t target "
699	    "[-u user -s secret] [-w timeout] [-e on | off]\n");
700	fprintf(stderr, "       iscsictl -A -d discovery-host "
701	    "[-u user -s secret] [-e on | off]\n");
702	fprintf(stderr, "       iscsictl -A -a [-c path]\n");
703	fprintf(stderr, "       iscsictl -A -n nickname [-c path]\n");
704	fprintf(stderr, "       iscsictl -M -i session-id [-p portal] "
705	    "[-t target] [-u user] [-s secret] [-e on | off]\n");
706	fprintf(stderr, "       iscsictl -M -i session-id -n nickname "
707	    "[-c path]\n");
708	fprintf(stderr, "       iscsictl -R [-p portal] [-t target]\n");
709	fprintf(stderr, "       iscsictl -R -a\n");
710	fprintf(stderr, "       iscsictl -R -n nickname [-c path]\n");
711	fprintf(stderr, "       iscsictl -L [-v] [-w timeout]\n");
712	exit(1);
713}
714
715int
716main(int argc, char **argv)
717{
718	int Aflag = 0, Mflag = 0, Rflag = 0, Lflag = 0, aflag = 0,
719	    rflag = 0, vflag = 0;
720	const char *conf_path = DEFAULT_CONFIG_PATH;
721	char *nickname = NULL, *discovery_host = NULL, *portal = NULL,
722	    *target = NULL, *user = NULL, *secret = NULL;
723	int timeout = -1, enable = ENABLE_UNSPECIFIED;
724	long long session_id = -1;
725	char *end;
726	int ch, error, iscsi_fd, retval, saved_errno;
727	int failed = 0;
728	struct conf *conf;
729	struct target *targ;
730
731	argc = xo_parse_args(argc, argv);
732	xo_open_container("iscsictl");
733
734	while ((ch = getopt(argc, argv, "AMRLac:d:e:i:n:p:rt:u:s:vw:")) != -1) {
735		switch (ch) {
736		case 'A':
737			Aflag = 1;
738			break;
739		case 'M':
740			Mflag = 1;
741			break;
742		case 'R':
743			Rflag = 1;
744			break;
745		case 'L':
746			Lflag = 1;
747			break;
748		case 'a':
749			aflag = 1;
750			break;
751		case 'c':
752			conf_path = optarg;
753			break;
754		case 'd':
755			discovery_host = optarg;
756			break;
757		case 'e':
758			enable = parse_enable(optarg);
759			if (enable == ENABLE_UNSPECIFIED) {
760				xo_errx(1, "invalid argument to -e, "
761				    "must be either \"on\" or \"off\"");
762			}
763			break;
764		case 'i':
765			session_id = strtol(optarg, &end, 10);
766			if ((size_t)(end - optarg) != strlen(optarg))
767				xo_errx(1, "trailing characters after session-id");
768			if (session_id < 0)
769				xo_errx(1, "session-id cannot be negative");
770			if (session_id > UINT_MAX)
771				xo_errx(1, "session-id cannot be greater than %u",
772				    UINT_MAX);
773			break;
774		case 'n':
775			nickname = optarg;
776			break;
777		case 'p':
778			portal = optarg;
779			break;
780		case 'r':
781			rflag = 1;
782			break;
783		case 't':
784			target = optarg;
785			break;
786		case 'u':
787			user = optarg;
788			break;
789		case 's':
790			secret = optarg;
791			break;
792		case 'v':
793			vflag = 1;
794			break;
795		case 'w':
796			timeout = strtol(optarg, &end, 10);
797			if ((size_t)(end - optarg) != strlen(optarg))
798				xo_errx(1, "trailing characters after timeout");
799			if (timeout < 0)
800				xo_errx(1, "timeout cannot be negative");
801			break;
802		case '?':
803		default:
804			usage();
805		}
806	}
807	argc -= optind;
808	if (argc != 0)
809		usage();
810
811	if (Aflag + Mflag + Rflag + Lflag == 0)
812		Lflag = 1;
813	if (Aflag + Mflag + Rflag + Lflag > 1)
814		xo_errx(1, "at most one of -A, -M, -R, or -L may be specified");
815
816	/*
817	 * Note that we ignore unnecessary/inapplicable "-c" flag; so that
818	 * people can do something like "alias ISCSICTL="iscsictl -c path"
819	 * in shell scripts.
820	 */
821	if (Aflag != 0) {
822		if (aflag != 0) {
823			if (enable != ENABLE_UNSPECIFIED)
824				xo_errx(1, "-a and -e are mutually exclusive");
825			if (portal != NULL)
826				xo_errx(1, "-a and -p are mutually exclusive");
827			if (target != NULL)
828				xo_errx(1, "-a and -t are mutually exclusive");
829			if (user != NULL)
830				xo_errx(1, "-a and -u are mutually exclusive");
831			if (secret != NULL)
832				xo_errx(1, "-a and -s are mutually exclusive");
833			if (nickname != NULL)
834				xo_errx(1, "-a and -n are mutually exclusive");
835			if (discovery_host != NULL)
836				xo_errx(1, "-a and -d are mutually exclusive");
837			if (rflag != 0)
838				xo_errx(1, "-a and -r are mutually exclusive");
839		} else if (nickname != NULL) {
840			if (enable != ENABLE_UNSPECIFIED)
841				xo_errx(1, "-n and -e are mutually exclusive");
842			if (portal != NULL)
843				xo_errx(1, "-n and -p are mutually exclusive");
844			if (target != NULL)
845				xo_errx(1, "-n and -t are mutually exclusive");
846			if (user != NULL)
847				xo_errx(1, "-n and -u are mutually exclusive");
848			if (secret != NULL)
849				xo_errx(1, "-n and -s are mutually exclusive");
850			if (discovery_host != NULL)
851				xo_errx(1, "-n and -d are mutually exclusive");
852			if (rflag != 0)
853				xo_errx(1, "-n and -r are mutually exclusive");
854		} else if (discovery_host != NULL) {
855			if (portal != NULL)
856				xo_errx(1, "-d and -p are mutually exclusive");
857			if (target != NULL)
858				xo_errx(1, "-d and -t are mutually exclusive");
859		} else {
860			if (target == NULL && portal == NULL)
861				xo_errx(1, "must specify -a, -n or -t/-p");
862
863			if (target != NULL && portal == NULL)
864				xo_errx(1, "-t must always be used with -p");
865			if (portal != NULL && target == NULL)
866				xo_errx(1, "-p must always be used with -t");
867		}
868
869		if (user != NULL && secret == NULL)
870			xo_errx(1, "-u must always be used with -s");
871		if (secret != NULL && user == NULL)
872			xo_errx(1, "-s must always be used with -u");
873
874		if (session_id != -1)
875			xo_errx(1, "-i cannot be used with -A");
876		if (vflag != 0)
877			xo_errx(1, "-v cannot be used with -A");
878
879	} else if (Mflag != 0) {
880		if (session_id == -1)
881			xo_errx(1, "-M requires -i");
882
883		if (nickname != NULL) {
884			if (enable != ENABLE_UNSPECIFIED)
885				xo_errx(1, "-n and -e are mutually exclusive");
886			if (portal != NULL)
887				xo_errx(1, "-n and -p are mutually exclusive");
888			if (target != NULL)
889				xo_errx(1, "-n and -t are mutually exclusive");
890			if (user != NULL)
891				xo_errx(1, "-n and -u are mutually exclusive");
892			if (secret != NULL)
893				xo_errx(1, "-n and -s are mutually exclusive");
894		}
895
896		if (aflag != 0)
897			xo_errx(1, "-a cannot be used with -M");
898		if (discovery_host != NULL)
899			xo_errx(1, "-d cannot be used with -M");
900		if (rflag != 0)
901			xo_errx(1, "-r cannot be used with -M");
902		if (vflag != 0)
903			xo_errx(1, "-v cannot be used with -M");
904		if (timeout != -1)
905			xo_errx(1, "-w cannot be used with -M");
906
907	} else if (Rflag != 0) {
908		if (aflag != 0) {
909			if (portal != NULL)
910				xo_errx(1, "-a and -p are mutually exclusive");
911			if (target != NULL)
912				xo_errx(1, "-a and -t are mutually exclusive");
913			if (nickname != NULL)
914				xo_errx(1, "-a and -n are mutually exclusive");
915		} else if (nickname != NULL) {
916			if (portal != NULL)
917				xo_errx(1, "-n and -p are mutually exclusive");
918			if (target != NULL)
919				xo_errx(1, "-n and -t are mutually exclusive");
920		} else if (target == NULL && portal == NULL) {
921			xo_errx(1, "must specify either -a, -n, -t, or -p");
922		}
923
924		if (discovery_host != NULL)
925			xo_errx(1, "-d cannot be used with -R");
926		if (enable != ENABLE_UNSPECIFIED)
927			xo_errx(1, "-e cannot be used with -R");
928		if (session_id != -1)
929			xo_errx(1, "-i cannot be used with -R");
930		if (rflag != 0)
931			xo_errx(1, "-r cannot be used with -R");
932		if (user != NULL)
933			xo_errx(1, "-u cannot be used with -R");
934		if (secret != NULL)
935			xo_errx(1, "-s cannot be used with -R");
936		if (vflag != 0)
937			xo_errx(1, "-v cannot be used with -R");
938		if (timeout != -1)
939			xo_errx(1, "-w cannot be used with -R");
940
941	} else {
942		assert(Lflag != 0);
943
944		if (discovery_host != NULL)
945			xo_errx(1, "-d cannot be used with -L");
946		if (session_id != -1)
947			xo_errx(1, "-i cannot be used with -L");
948		if (nickname != NULL)
949			xo_errx(1, "-n cannot be used with -L");
950		if (portal != NULL)
951			xo_errx(1, "-p cannot be used with -L");
952		if (rflag != 0)
953			xo_errx(1, "-r cannot be used with -L");
954		if (target != NULL)
955			xo_errx(1, "-t cannot be used with -L");
956		if (user != NULL)
957			xo_errx(1, "-u cannot be used with -L");
958		if (secret != NULL)
959			xo_errx(1, "-s cannot be used with -L");
960	}
961
962	iscsi_fd = open(ISCSI_PATH, O_RDWR);
963	if (iscsi_fd < 0 && errno == ENOENT) {
964		saved_errno = errno;
965		retval = kldload("iscsi");
966		if (retval != -1)
967			iscsi_fd = open(ISCSI_PATH, O_RDWR);
968		else
969			errno = saved_errno;
970	}
971	if (iscsi_fd < 0)
972		xo_err(1, "failed to open %s", ISCSI_PATH);
973
974	if (Aflag != 0 && aflag != 0) {
975		conf = conf_new_from_file(conf_path);
976
977		TAILQ_FOREACH(targ, &conf->conf_targets, t_next)
978			failed += kernel_add(iscsi_fd, targ);
979	} else if (nickname != NULL) {
980		conf = conf_new_from_file(conf_path);
981		targ = target_find(conf, nickname);
982		if (targ == NULL)
983			xo_errx(1, "target %s not found in %s",
984			    nickname, conf_path);
985
986		if (Aflag != 0)
987			failed += kernel_add(iscsi_fd, targ);
988		else if (Mflag != 0)
989			failed += kernel_modify(iscsi_fd, session_id, targ);
990		else if (Rflag != 0)
991			failed += kernel_remove(iscsi_fd, targ);
992		else
993			failed += kernel_list(iscsi_fd, targ, vflag);
994	} else if (Mflag != 0) {
995		kernel_modify_some(iscsi_fd, session_id, target, portal,
996		    user, secret, enable);
997	} else {
998		if (Aflag != 0 && target != NULL) {
999			if (valid_iscsi_name(target) == false)
1000				xo_errx(1, "invalid target name \"%s\"", target);
1001		}
1002		conf = conf_new();
1003		targ = target_new(conf);
1004		targ->t_initiator_name = default_initiator_name();
1005		targ->t_header_digest = DIGEST_NONE;
1006		targ->t_data_digest = DIGEST_NONE;
1007		targ->t_name = target;
1008		if (discovery_host != NULL) {
1009			targ->t_session_type = SESSION_TYPE_DISCOVERY;
1010			targ->t_address = discovery_host;
1011		} else {
1012			targ->t_session_type = SESSION_TYPE_NORMAL;
1013			targ->t_address = portal;
1014		}
1015		targ->t_enable = enable;
1016		if (rflag != 0)
1017			targ->t_protocol = PROTOCOL_ISER;
1018		targ->t_user = user;
1019		targ->t_secret = secret;
1020
1021		if (Aflag != 0)
1022			failed += kernel_add(iscsi_fd, targ);
1023		else if (Rflag != 0)
1024			failed += kernel_remove(iscsi_fd, targ);
1025		else
1026			failed += kernel_list(iscsi_fd, targ, vflag);
1027	}
1028
1029	if (timeout != -1)
1030		failed += kernel_wait(iscsi_fd, timeout);
1031
1032	error = close(iscsi_fd);
1033	if (error != 0)
1034		xo_err(1, "close");
1035
1036	xo_close_container("iscsictl");
1037	xo_finish();
1038
1039	if (failed != 0)
1040		return (1);
1041
1042	return (0);
1043}
1044