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