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