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