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