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