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 targ->t_pingtimeout = -1;
90 targ->t_logintimeout = -1;
91 TAILQ_INSERT_TAIL(&conf->conf_targets, targ, t_next);
92
93 return (targ);
94 }
95
96 void
target_delete(struct target * targ)97 target_delete(struct target *targ)
98 {
99
100 TAILQ_REMOVE(&targ->t_conf->conf_targets, targ, t_next);
101 free(targ);
102 }
103
104 static char *
default_initiator_name(void)105 default_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
125 static bool
valid_hex(const char ch)126 valid_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
157 int
parse_enable(const char * enable)158 parse_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
174 bool
valid_iscsi_name(const char * name)175 valid_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
242 void
conf_verify(struct conf * conf)243 conf_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
309 static void
conf_from_target(struct iscsi_session_conf * conf,const struct target * targ)310 conf_from_target(struct iscsi_session_conf *conf,
311 const struct target *targ)
312 {
313 memset(conf, 0, sizeof(*conf));
314
315 /*
316 * XXX: Check bounds and return error instead of silently truncating.
317 */
318 if (targ->t_initiator_name != NULL)
319 strlcpy(conf->isc_initiator, targ->t_initiator_name,
320 sizeof(conf->isc_initiator));
321 if (targ->t_initiator_address != NULL)
322 strlcpy(conf->isc_initiator_addr, targ->t_initiator_address,
323 sizeof(conf->isc_initiator_addr));
324 if (targ->t_initiator_alias != NULL)
325 strlcpy(conf->isc_initiator_alias, targ->t_initiator_alias,
326 sizeof(conf->isc_initiator_alias));
327 if (targ->t_name != NULL)
328 strlcpy(conf->isc_target, targ->t_name,
329 sizeof(conf->isc_target));
330 if (targ->t_address != NULL)
331 strlcpy(conf->isc_target_addr, targ->t_address,
332 sizeof(conf->isc_target_addr));
333 if (targ->t_user != NULL)
334 strlcpy(conf->isc_user, targ->t_user,
335 sizeof(conf->isc_user));
336 if (targ->t_secret != NULL)
337 strlcpy(conf->isc_secret, targ->t_secret,
338 sizeof(conf->isc_secret));
339 if (targ->t_mutual_user != NULL)
340 strlcpy(conf->isc_mutual_user, targ->t_mutual_user,
341 sizeof(conf->isc_mutual_user));
342 if (targ->t_mutual_secret != NULL)
343 strlcpy(conf->isc_mutual_secret, targ->t_mutual_secret,
344 sizeof(conf->isc_mutual_secret));
345 if (targ->t_session_type == SESSION_TYPE_DISCOVERY)
346 conf->isc_discovery = 1;
347 if (targ->t_enable != ENABLE_OFF)
348 conf->isc_enable = 1;
349 if (targ->t_protocol == PROTOCOL_ISER)
350 conf->isc_iser = 1;
351 if (targ->t_offload != NULL)
352 strlcpy(conf->isc_offload, targ->t_offload,
353 sizeof(conf->isc_offload));
354 if (targ->t_header_digest == DIGEST_CRC32C)
355 conf->isc_header_digest = ISCSI_DIGEST_CRC32C;
356 else
357 conf->isc_header_digest = ISCSI_DIGEST_NONE;
358 if (targ->t_data_digest == DIGEST_CRC32C)
359 conf->isc_data_digest = ISCSI_DIGEST_CRC32C;
360 else
361 conf->isc_data_digest = ISCSI_DIGEST_NONE;
362 conf->isc_dscp = targ->t_dscp;
363 conf->isc_pcp = targ->t_pcp;
364 conf->isc_ping_timeout = targ->t_pingtimeout;
365 conf->isc_login_timeout = targ->t_logintimeout;
366 }
367
368 static int
kernel_add(int iscsi_fd,const struct target * targ)369 kernel_add(int iscsi_fd, const struct target *targ)
370 {
371 struct iscsi_session_add isa;
372 int error;
373
374 memset(&isa, 0, sizeof(isa));
375 conf_from_target(&isa.isa_conf, targ);
376 error = ioctl(iscsi_fd, ISCSISADD, &isa);
377 if (error != 0)
378 xo_warn("ISCSISADD");
379 return (error);
380 }
381
382 static int
kernel_modify(int iscsi_fd,unsigned int session_id,const struct target * targ)383 kernel_modify(int iscsi_fd, unsigned int session_id, const struct target *targ)
384 {
385 struct iscsi_session_modify ism;
386 int error;
387
388 memset(&ism, 0, sizeof(ism));
389 ism.ism_session_id = session_id;
390 conf_from_target(&ism.ism_conf, targ);
391 error = ioctl(iscsi_fd, ISCSISMODIFY, &ism);
392 if (error != 0)
393 xo_warn("ISCSISMODIFY");
394 return (error);
395 }
396
397 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)398 kernel_modify_some(int iscsi_fd, unsigned int session_id, const char *target,
399 const char *target_addr, const char *user, const char *secret, int enable)
400 {
401 struct iscsi_session_state *states = NULL;
402 struct iscsi_session_state *state;
403 struct iscsi_session_conf *conf;
404 struct iscsi_session_list isl;
405 struct iscsi_session_modify ism;
406 unsigned int i, nentries = 1;
407 int error;
408
409 for (;;) {
410 states = realloc(states,
411 nentries * sizeof(struct iscsi_session_state));
412 if (states == NULL)
413 xo_err(1, "realloc");
414
415 memset(&isl, 0, sizeof(isl));
416 isl.isl_nentries = nentries;
417 isl.isl_pstates = states;
418
419 error = ioctl(iscsi_fd, ISCSISLIST, &isl);
420 if (error != 0 && errno == EMSGSIZE) {
421 nentries *= 4;
422 continue;
423 }
424 break;
425 }
426 if (error != 0)
427 xo_errx(1, "ISCSISLIST");
428
429 for (i = 0; i < isl.isl_nentries; i++) {
430 state = &states[i];
431
432 if (state->iss_id == session_id)
433 break;
434 }
435 if (i == isl.isl_nentries)
436 xo_errx(1, "session-id %u not found", session_id);
437
438 conf = &state->iss_conf;
439
440 if (target != NULL)
441 strlcpy(conf->isc_target, target, sizeof(conf->isc_target));
442 if (target_addr != NULL)
443 strlcpy(conf->isc_target_addr, target_addr,
444 sizeof(conf->isc_target_addr));
445 if (user != NULL)
446 strlcpy(conf->isc_user, user, sizeof(conf->isc_user));
447 if (secret != NULL)
448 strlcpy(conf->isc_secret, secret, sizeof(conf->isc_secret));
449 if (enable == ENABLE_ON)
450 conf->isc_enable = 1;
451 else if (enable == ENABLE_OFF)
452 conf->isc_enable = 0;
453
454 memset(&ism, 0, sizeof(ism));
455 ism.ism_session_id = session_id;
456 memcpy(&ism.ism_conf, conf, sizeof(ism.ism_conf));
457 error = ioctl(iscsi_fd, ISCSISMODIFY, &ism);
458 if (error != 0)
459 xo_warn("ISCSISMODIFY");
460 }
461
462 static int
kernel_remove(int iscsi_fd,const struct target * targ)463 kernel_remove(int iscsi_fd, const struct target *targ)
464 {
465 struct iscsi_session_remove isr;
466 int error;
467
468 memset(&isr, 0, sizeof(isr));
469 conf_from_target(&isr.isr_conf, targ);
470 error = ioctl(iscsi_fd, ISCSISREMOVE, &isr);
471 if (error != 0)
472 xo_warn("ISCSISREMOVE");
473 return (error);
474 }
475
476 /*
477 * XXX: Add filtering.
478 */
479 static int
kernel_list(int iscsi_fd,const struct target * targ __unused,int verbose)480 kernel_list(int iscsi_fd, const struct target *targ __unused,
481 int verbose)
482 {
483 struct iscsi_session_state *states = NULL;
484 const struct iscsi_session_state *state;
485 const struct iscsi_session_conf *conf;
486 struct iscsi_session_list isl;
487 unsigned int i, nentries = 1;
488 int error;
489
490 for (;;) {
491 states = realloc(states,
492 nentries * sizeof(struct iscsi_session_state));
493 if (states == NULL)
494 xo_err(1, "realloc");
495
496 memset(&isl, 0, sizeof(isl));
497 isl.isl_nentries = nentries;
498 isl.isl_pstates = states;
499
500 error = ioctl(iscsi_fd, ISCSISLIST, &isl);
501 if (error != 0 && errno == EMSGSIZE) {
502 nentries *= 4;
503 continue;
504 }
505 break;
506 }
507 if (error != 0) {
508 xo_warn("ISCSISLIST");
509 return (error);
510 }
511
512 if (verbose != 0) {
513 xo_open_list("session");
514 for (i = 0; i < isl.isl_nentries; i++) {
515 state = &states[i];
516 conf = &state->iss_conf;
517
518 xo_open_instance("session");
519
520 /*
521 * Display-only modifier as this information
522 * is also present within the 'session' container
523 */
524 xo_emit("{L:/%-26s}{V:sessionId/%u}\n",
525 "Session ID:", state->iss_id);
526
527 xo_open_container("initiator");
528 xo_emit("{L:/%-26s}{V:name/%s}\n",
529 "Initiator name:", conf->isc_initiator);
530 xo_emit("{L:/%-26s}{V:portal/%s}\n",
531 "Initiator portal:", conf->isc_initiator_addr);
532 xo_emit("{L:/%-26s}{V:alias/%s}\n",
533 "Initiator alias:", conf->isc_initiator_alias);
534 xo_close_container("initiator");
535
536 xo_open_container("target");
537 xo_emit("{L:/%-26s}{V:name/%s}\n",
538 "Target name:", conf->isc_target);
539 xo_emit("{L:/%-26s}{V:portal/%s}\n",
540 "Target portal:", conf->isc_target_addr);
541 xo_emit("{L:/%-26s}{V:alias/%s}\n",
542 "Target alias:", state->iss_target_alias);
543 if (conf->isc_dscp != -1)
544 xo_emit("{L:/%-26s}{V:dscp/0x%02x}\n",
545 "Target DSCP:", conf->isc_dscp);
546 if (conf->isc_pcp != -1)
547 xo_emit("{L:/%-26s}{V:pcp/0x%02x}\n",
548 "Target PCP:", conf->isc_pcp);
549 if (conf->isc_ping_timeout != -1)
550 xo_emit("{L:/%-26s}{V:PingTimeout/%d}\n",
551 "Target PingTimeout:",
552 conf->isc_ping_timeout);
553 if (conf->isc_login_timeout != -1)
554 xo_emit("{L:/%-26s}{V:LoginTimeout/%d}\n",
555 "Target LoginTimeout:",
556 conf->isc_login_timeout);
557 xo_close_container("target");
558
559 xo_open_container("auth");
560 xo_emit("{L:/%-26s}{V:user/%s}\n",
561 "User:", conf->isc_user);
562 xo_emit("{L:/%-26s}{V:secret/%s}\n",
563 "Secret:", conf->isc_secret);
564 xo_emit("{L:/%-26s}{V:mutualUser/%s}\n",
565 "Mutual user:", conf->isc_mutual_user);
566 xo_emit("{L:/%-26s}{V:mutualSecret/%s}\n",
567 "Mutual secret:", conf->isc_mutual_secret);
568 xo_close_container("auth");
569
570 xo_emit("{L:/%-26s}{V:type/%s}\n",
571 "Session type:",
572 conf->isc_discovery ? "Discovery" : "Normal");
573 xo_emit("{L:/%-26s}{V:enable/%s}\n",
574 "Enable:",
575 conf->isc_enable ? "Yes" : "No");
576 xo_emit("{L:/%-26s}{V:state/%s}\n",
577 "Session state:",
578 state->iss_connected ? "Connected" : "Disconnected");
579 xo_emit("{L:/%-26s}{V:failureReason/%s}\n",
580 "Failure reason:", state->iss_reason);
581 xo_emit("{L:/%-26s}{V:headerDigest/%s}\n",
582 "Header digest:",
583 state->iss_header_digest == ISCSI_DIGEST_CRC32C ?
584 "CRC32C" : "None");
585 xo_emit("{L:/%-26s}{V:dataDigest/%s}\n",
586 "Data digest:",
587 state->iss_data_digest == ISCSI_DIGEST_CRC32C ?
588 "CRC32C" : "None");
589 xo_emit("{L:/%-26s}{V:recvDataSegmentLen/%d}\n",
590 "MaxRecvDataSegmentLength:",
591 state->iss_max_recv_data_segment_length);
592 xo_emit("{L:/%-26s}{V:sendDataSegmentLen/%d}\n",
593 "MaxSendDataSegmentLength:",
594 state->iss_max_send_data_segment_length);
595 xo_emit("{L:/%-26s}{V:maxBurstLen/%d}\n",
596 "MaxBurstLen:", state->iss_max_burst_length);
597 xo_emit("{L:/%-26s}{V:firstBurstLen/%d}\n",
598 "FirstBurstLen:", state->iss_first_burst_length);
599 xo_emit("{L:/%-26s}{V:immediateData/%s}\n",
600 "ImmediateData:", state->iss_immediate_data ? "Yes" : "No");
601 xo_emit("{L:/%-26s}{V:iSER/%s}\n",
602 "iSER (RDMA):", conf->isc_iser ? "Yes" : "No");
603 xo_emit("{L:/%-26s}{V:offloadDriver/%s}\n",
604 "Offload driver:", state->iss_offload);
605 xo_emit("{L:/%-26s}",
606 "Device nodes:");
607 print_periphs(state->iss_id);
608 xo_emit("\n\n");
609 xo_close_instance("session");
610 }
611 xo_close_list("session");
612 } else {
613 xo_emit("{T:/%-36s} {T:/%-16s} {T:/%s}\n",
614 "Target name", "Target portal", "State");
615
616 if (isl.isl_nentries != 0)
617 xo_open_list("session");
618 for (i = 0; i < isl.isl_nentries; i++) {
619
620 state = &states[i];
621 conf = &state->iss_conf;
622
623 xo_open_instance("session");
624 xo_emit("{V:name/%-36s/%s} {V:portal/%-16s/%s} ",
625 conf->isc_target, conf->isc_target_addr);
626
627 if (state->iss_reason[0] != '\0' &&
628 conf->isc_enable != 0) {
629 xo_emit("{V:state/%s}\n", state->iss_reason);
630 } else {
631 if (conf->isc_discovery) {
632 xo_emit("{V:state}\n", "Discovery");
633 } else if (conf->isc_enable == 0) {
634 xo_emit("{V:state}\n", "Disabled");
635 } else if (state->iss_connected) {
636 xo_emit("{V:state}: ", "Connected");
637 print_periphs(state->iss_id);
638 xo_emit("\n");
639 } else {
640 xo_emit("{V:state}\n", "Disconnected");
641 }
642 }
643 xo_close_instance("session");
644 }
645 if (isl.isl_nentries != 0)
646 xo_close_list("session");
647 }
648
649 return (0);
650 }
651
652 static int
kernel_wait(int iscsi_fd,int timeout)653 kernel_wait(int iscsi_fd, int timeout)
654 {
655 struct iscsi_session_state *states = NULL;
656 const struct iscsi_session_state *state;
657 struct iscsi_session_list isl;
658 unsigned int i, nentries = 1;
659 bool all_connected;
660 int error;
661
662 for (;;) {
663 for (;;) {
664 states = realloc(states,
665 nentries * sizeof(struct iscsi_session_state));
666 if (states == NULL)
667 xo_err(1, "realloc");
668
669 memset(&isl, 0, sizeof(isl));
670 isl.isl_nentries = nentries;
671 isl.isl_pstates = states;
672
673 error = ioctl(iscsi_fd, ISCSISLIST, &isl);
674 if (error != 0 && errno == EMSGSIZE) {
675 nentries *= 4;
676 continue;
677 }
678 break;
679 }
680 if (error != 0) {
681 xo_warn("ISCSISLIST");
682 return (error);
683 }
684
685 all_connected = true;
686 for (i = 0; i < isl.isl_nentries; i++) {
687 state = &states[i];
688
689 if (!state->iss_connected) {
690 all_connected = false;
691 break;
692 }
693 }
694
695 if (all_connected)
696 return (0);
697
698 sleep(1);
699
700 if (timeout > 0) {
701 timeout--;
702 if (timeout == 0)
703 return (1);
704 }
705 }
706 }
707
708 static void
usage(void)709 usage(void)
710 {
711
712 fprintf(stderr, "usage: iscsictl -A -p portal -t target "
713 "[-u user -s secret] [-w timeout] [-e on | off]\n");
714 fprintf(stderr, " iscsictl -A -d discovery-host "
715 "[-u user -s secret] [-e on | off]\n");
716 fprintf(stderr, " iscsictl -A -a [-c path]\n");
717 fprintf(stderr, " iscsictl -A -n nickname [-c path]\n");
718 fprintf(stderr, " iscsictl -M -i session-id [-p portal] "
719 "[-t target] [-u user] [-s secret] [-e on | off]\n");
720 fprintf(stderr, " iscsictl -M -i session-id -n nickname "
721 "[-c path]\n");
722 fprintf(stderr, " iscsictl -R [-p portal] [-t target]\n");
723 fprintf(stderr, " iscsictl -R -a\n");
724 fprintf(stderr, " iscsictl -R -n nickname [-c path]\n");
725 fprintf(stderr, " iscsictl -L [-v] [-w timeout]\n");
726 exit(1);
727 }
728
729 int
main(int argc,char ** argv)730 main(int argc, char **argv)
731 {
732 int Aflag = 0, Mflag = 0, Rflag = 0, Lflag = 0, aflag = 0,
733 rflag = 0, vflag = 0;
734 const char *conf_path = DEFAULT_CONFIG_PATH;
735 char *nickname = NULL, *discovery_host = NULL, *portal = NULL,
736 *target = NULL, *user = NULL, *secret = NULL;
737 int timeout = -1, enable = ENABLE_UNSPECIFIED;
738 long long session_id = -1;
739 char *end;
740 int ch, error, iscsi_fd, retval, saved_errno;
741 int failed = 0;
742 struct conf *conf;
743 struct target *targ;
744
745 argc = xo_parse_args(argc, argv);
746 if (argc < 0)
747 exit(1);
748
749 xo_open_container("iscsictl");
750
751 while ((ch = getopt(argc, argv, "AMRLac:d:e:i:n:p:rt:u:s:vw:")) != -1) {
752 switch (ch) {
753 case 'A':
754 Aflag = 1;
755 break;
756 case 'M':
757 Mflag = 1;
758 break;
759 case 'R':
760 Rflag = 1;
761 break;
762 case 'L':
763 Lflag = 1;
764 break;
765 case 'a':
766 aflag = 1;
767 break;
768 case 'c':
769 conf_path = optarg;
770 break;
771 case 'd':
772 discovery_host = optarg;
773 break;
774 case 'e':
775 enable = parse_enable(optarg);
776 if (enable == ENABLE_UNSPECIFIED) {
777 xo_errx(1, "invalid argument to -e, "
778 "must be either \"on\" or \"off\"");
779 }
780 break;
781 case 'i':
782 session_id = strtol(optarg, &end, 10);
783 if ((size_t)(end - optarg) != strlen(optarg))
784 xo_errx(1, "trailing characters after session-id");
785 if (session_id < 0)
786 xo_errx(1, "session-id cannot be negative");
787 if (session_id > UINT_MAX)
788 xo_errx(1, "session-id cannot be greater than %u",
789 UINT_MAX);
790 break;
791 case 'n':
792 nickname = optarg;
793 break;
794 case 'p':
795 portal = optarg;
796 break;
797 case 'r':
798 rflag = 1;
799 break;
800 case 't':
801 target = optarg;
802 break;
803 case 'u':
804 user = optarg;
805 break;
806 case 's':
807 secret = optarg;
808 break;
809 case 'v':
810 vflag = 1;
811 break;
812 case 'w':
813 timeout = strtol(optarg, &end, 10);
814 if ((size_t)(end - optarg) != strlen(optarg))
815 xo_errx(1, "trailing characters after timeout");
816 if (timeout < 0)
817 xo_errx(1, "timeout cannot be negative");
818 break;
819 case '?':
820 default:
821 usage();
822 }
823 }
824 argc -= optind;
825 if (argc != 0)
826 usage();
827
828 if (Aflag + Mflag + Rflag + Lflag == 0)
829 Lflag = 1;
830 if (Aflag + Mflag + Rflag + Lflag > 1)
831 xo_errx(1, "at most one of -A, -M, -R, or -L may be specified");
832
833 /*
834 * Note that we ignore unnecessary/inapplicable "-c" flag; so that
835 * people can do something like "alias ISCSICTL="iscsictl -c path"
836 * in shell scripts.
837 */
838 if (Aflag != 0) {
839 if (aflag != 0) {
840 if (enable != ENABLE_UNSPECIFIED)
841 xo_errx(1, "-a and -e are mutually exclusive");
842 if (portal != NULL)
843 xo_errx(1, "-a and -p are mutually exclusive");
844 if (target != NULL)
845 xo_errx(1, "-a and -t are mutually exclusive");
846 if (user != NULL)
847 xo_errx(1, "-a and -u are mutually exclusive");
848 if (secret != NULL)
849 xo_errx(1, "-a and -s are mutually exclusive");
850 if (nickname != NULL)
851 xo_errx(1, "-a and -n are mutually exclusive");
852 if (discovery_host != NULL)
853 xo_errx(1, "-a and -d are mutually exclusive");
854 if (rflag != 0)
855 xo_errx(1, "-a and -r are mutually exclusive");
856 } else if (nickname != NULL) {
857 if (enable != ENABLE_UNSPECIFIED)
858 xo_errx(1, "-n and -e are mutually exclusive");
859 if (portal != NULL)
860 xo_errx(1, "-n and -p are mutually exclusive");
861 if (target != NULL)
862 xo_errx(1, "-n and -t are mutually exclusive");
863 if (user != NULL)
864 xo_errx(1, "-n and -u are mutually exclusive");
865 if (secret != NULL)
866 xo_errx(1, "-n and -s are mutually exclusive");
867 if (discovery_host != NULL)
868 xo_errx(1, "-n and -d are mutually exclusive");
869 if (rflag != 0)
870 xo_errx(1, "-n and -r are mutually exclusive");
871 } else if (discovery_host != NULL) {
872 if (portal != NULL)
873 xo_errx(1, "-d and -p are mutually exclusive");
874 if (target != NULL)
875 xo_errx(1, "-d and -t are mutually exclusive");
876 } else {
877 if (target == NULL && portal == NULL)
878 xo_errx(1, "must specify -a, -n or -t/-p");
879
880 if (target != NULL && portal == NULL)
881 xo_errx(1, "-t must always be used with -p");
882 if (portal != NULL && target == NULL)
883 xo_errx(1, "-p must always be used with -t");
884 }
885
886 if (user != NULL && secret == NULL)
887 xo_errx(1, "-u must always be used with -s");
888 if (secret != NULL && user == NULL)
889 xo_errx(1, "-s must always be used with -u");
890
891 if (session_id != -1)
892 xo_errx(1, "-i cannot be used with -A");
893 if (vflag != 0)
894 xo_errx(1, "-v cannot be used with -A");
895
896 } else if (Mflag != 0) {
897 if (session_id == -1)
898 xo_errx(1, "-M requires -i");
899
900 if (nickname != NULL) {
901 if (enable != ENABLE_UNSPECIFIED)
902 xo_errx(1, "-n and -e are mutually exclusive");
903 if (portal != NULL)
904 xo_errx(1, "-n and -p are mutually exclusive");
905 if (target != NULL)
906 xo_errx(1, "-n and -t are mutually exclusive");
907 if (user != NULL)
908 xo_errx(1, "-n and -u are mutually exclusive");
909 if (secret != NULL)
910 xo_errx(1, "-n and -s are mutually exclusive");
911 }
912
913 if (aflag != 0)
914 xo_errx(1, "-a cannot be used with -M");
915 if (discovery_host != NULL)
916 xo_errx(1, "-d cannot be used with -M");
917 if (rflag != 0)
918 xo_errx(1, "-r cannot be used with -M");
919 if (vflag != 0)
920 xo_errx(1, "-v cannot be used with -M");
921 if (timeout != -1)
922 xo_errx(1, "-w cannot be used with -M");
923
924 } else if (Rflag != 0) {
925 if (aflag != 0) {
926 if (portal != NULL)
927 xo_errx(1, "-a and -p are mutually exclusive");
928 if (target != NULL)
929 xo_errx(1, "-a and -t are mutually exclusive");
930 if (nickname != NULL)
931 xo_errx(1, "-a and -n are mutually exclusive");
932 } else if (nickname != NULL) {
933 if (portal != NULL)
934 xo_errx(1, "-n and -p are mutually exclusive");
935 if (target != NULL)
936 xo_errx(1, "-n and -t are mutually exclusive");
937 } else if (target == NULL && portal == NULL) {
938 xo_errx(1, "must specify either -a, -n, -t, or -p");
939 }
940
941 if (discovery_host != NULL)
942 xo_errx(1, "-d cannot be used with -R");
943 if (enable != ENABLE_UNSPECIFIED)
944 xo_errx(1, "-e cannot be used with -R");
945 if (session_id != -1)
946 xo_errx(1, "-i cannot be used with -R");
947 if (rflag != 0)
948 xo_errx(1, "-r cannot be used with -R");
949 if (user != NULL)
950 xo_errx(1, "-u cannot be used with -R");
951 if (secret != NULL)
952 xo_errx(1, "-s cannot be used with -R");
953 if (vflag != 0)
954 xo_errx(1, "-v cannot be used with -R");
955 if (timeout != -1)
956 xo_errx(1, "-w cannot be used with -R");
957
958 } else {
959 assert(Lflag != 0);
960
961 if (discovery_host != NULL)
962 xo_errx(1, "-d cannot be used with -L");
963 if (session_id != -1)
964 xo_errx(1, "-i cannot be used with -L");
965 if (nickname != NULL)
966 xo_errx(1, "-n cannot be used with -L");
967 if (portal != NULL)
968 xo_errx(1, "-p cannot be used with -L");
969 if (rflag != 0)
970 xo_errx(1, "-r cannot be used with -L");
971 if (target != NULL)
972 xo_errx(1, "-t cannot be used with -L");
973 if (user != NULL)
974 xo_errx(1, "-u cannot be used with -L");
975 if (secret != NULL)
976 xo_errx(1, "-s cannot be used with -L");
977 }
978
979 iscsi_fd = open(ISCSI_PATH, O_RDWR);
980 if (iscsi_fd < 0 && errno == ENOENT) {
981 saved_errno = errno;
982 retval = kldload("iscsi");
983 if (retval != -1)
984 iscsi_fd = open(ISCSI_PATH, O_RDWR);
985 else
986 errno = saved_errno;
987 }
988 if (iscsi_fd < 0)
989 xo_err(1, "failed to open %s", ISCSI_PATH);
990
991 if (Aflag != 0 && aflag != 0) {
992 conf = conf_new_from_file(conf_path);
993
994 TAILQ_FOREACH(targ, &conf->conf_targets, t_next)
995 failed += kernel_add(iscsi_fd, targ);
996 } else if (nickname != NULL) {
997 conf = conf_new_from_file(conf_path);
998 targ = target_find(conf, nickname);
999 if (targ == NULL)
1000 xo_errx(1, "target %s not found in %s",
1001 nickname, conf_path);
1002
1003 if (Aflag != 0)
1004 failed += kernel_add(iscsi_fd, targ);
1005 else if (Mflag != 0)
1006 failed += kernel_modify(iscsi_fd, session_id, targ);
1007 else if (Rflag != 0)
1008 failed += kernel_remove(iscsi_fd, targ);
1009 else
1010 failed += kernel_list(iscsi_fd, targ, vflag);
1011 } else if (Mflag != 0) {
1012 kernel_modify_some(iscsi_fd, session_id, target, portal,
1013 user, secret, enable);
1014 } else {
1015 if (Aflag != 0 && target != NULL) {
1016 if (valid_iscsi_name(target) == false)
1017 xo_errx(1, "invalid target name \"%s\"", target);
1018 }
1019 conf = conf_new();
1020 targ = target_new(conf);
1021 targ->t_initiator_name = default_initiator_name();
1022 targ->t_header_digest = DIGEST_NONE;
1023 targ->t_data_digest = DIGEST_NONE;
1024 targ->t_name = target;
1025 if (discovery_host != NULL) {
1026 targ->t_session_type = SESSION_TYPE_DISCOVERY;
1027 targ->t_address = discovery_host;
1028 } else {
1029 targ->t_session_type = SESSION_TYPE_NORMAL;
1030 targ->t_address = portal;
1031 }
1032 targ->t_enable = enable;
1033 if (rflag != 0)
1034 targ->t_protocol = PROTOCOL_ISER;
1035 targ->t_user = user;
1036 targ->t_secret = secret;
1037
1038 if (Aflag != 0)
1039 failed += kernel_add(iscsi_fd, targ);
1040 else if (Rflag != 0)
1041 failed += kernel_remove(iscsi_fd, targ);
1042 else
1043 failed += kernel_list(iscsi_fd, targ, vflag);
1044 }
1045
1046 if (timeout != -1)
1047 failed += kernel_wait(iscsi_fd, timeout);
1048
1049 error = close(iscsi_fd);
1050 if (error != 0)
1051 xo_err(1, "close");
1052
1053 xo_close_container("iscsictl");
1054 xo_finish();
1055
1056 if (failed != 0)
1057 return (1);
1058
1059 return (0);
1060 }
1061