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