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 } 981