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