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