Index: head/usr.bin/iscsictl/iscsictl.8 =================================================================== --- head/usr.bin/iscsictl/iscsictl.8 (revision 289452) +++ head/usr.bin/iscsictl/iscsictl.8 (revision 289453) @@ -1,187 +1,196 @@ .\" Copyright (c) 2012 The FreeBSD Foundation .\" All rights reserved. .\" .\" This software was developed by Edward Tomasz Napierala under sponsorship .\" from the FreeBSD Foundation. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" $FreeBSD$ .\" -.Dd September 12, 2014 +.Dd October 17, 2015 .Dt ISCSICTL 8 .Os .Sh NAME .Nm iscsictl .Nd iSCSI initiator management utility .Sh SYNOPSIS .Nm .Fl A -.Fl p Ar portal Fl t Ar target Op Fl u Ar user Fl s Ar secret +.Fl p Ar portal Fl t Ar target +.Op Fl u Ar user Fl s Ar secret +.Op Fl w Ar timeout .Nm .Fl A .Fl d Ar discovery-host Op Fl u Ar user Fl s Ar secret .Nm .Fl A .Fl a Op Fl c Ar path .Nm .Fl A .Fl n Ar nickname Op Fl c Ar path .Nm .Fl M .Fl i Ar session-id .Op Fl p Ar portal .Op Fl t Ar target .Op Fl u Ar user .Op Fl s Ar secret .Nm .Fl M .Fl i Ar session-id .Op Fl n Ar nickname Op Fl c Ar path .Nm .Fl R .Op Fl p Ar portal .Op Fl t Ar target .Nm .Fl R .Fl a .Nm .Fl R .Fl n Ar nickname Op Fl c Ar path .Nm .Fl L .Op Fl v +.Op Fl w Ar timeout .Sh DESCRIPTION The .Nm utility is used to configure the iSCSI initiator. .Pp The following options are available: .Bl -tag -width ".Fl A" .It Fl A Add session. .It Fl M Modify session. .It Fl R Remove session. .It Fl L List sessions. .It Fl a When adding, add all sessions defined in the configuration file. When removing, remove all currently established sessions. .It Fl c Path to the configuration file. The default is .Pa /etc/iscsi.conf . .It Fl d Target host name or address used for SendTargets discovery. When used, it will add a temporary discovery session. After discovery is done, sessions will be added for each discovered target, and the temporary discovery session will be removed. .It Fl i Session ID, as displayed by .Nm .Fl v . .It Fl n The "nickname" of session defined in the configuration file. .It Fl p Target portal - host name or address - for statically defined targets. .It Fl s CHAP secret. .It Fl t Target name. .It Fl u CHAP login. .It Fl v Verbose mode. +.It Fl w +Instead of returning immediately, wait up to +.Ar timeout +seconds until all configured sessions are successfully established. .El .Pp Certain parameters are necessary when adding a session. One can specify these either via command line (using the .Fl t , .Fl p , .Fl u , and .Fl s options), or configuration file (using the .Fl a or .Fl n options). Some functionality - for example mutual CHAP - is available only via configuration file. .Pp Since connecting to the target is performed in background, non-zero exit status does not mean that the session was successfully established. -Use +Use either .Nm Fl L -to check the connection status. +to check the connection status, or the +.Fl w +flag to wait for session establishment. .Pp Note that in order for the iSCSI initiator to be able to connect to a target, the .Xr iscsid 8 daemon must be running. .Pp Also note that .Fx currently supports two different initiators: the old one, .Xr iscsi_initiator 4 , with its control utility .Xr iscontrol 8 , and the new one, .Xr iscsi 4 , with .Nm and .Xr iscsid 8 . The only thing the two have in common is the configuration file, .Xr iscsi.conf 5 . .Sh FILES .Bl -tag -width ".Pa /etc/iscsi.conf" -compact .It Pa /etc/iscsi.conf iSCSI initiator configuration file. .El .Sh EXIT STATUS The .Nm utility exits 0 on success, and >0 if an error occurs. .Sh EXAMPLES Attach to target iqn.2012-06.com.example:target0, served by 192.168.1.1: .Dl Nm Fl A Fl t Ar iqn.2012-06.com.example:target0 Fl p Ar 192.168.1.1 .Pp Disconnect all iSCSI sessions: .Dl Nm Fl Ra .Sh SEE ALSO .Xr iscsi 4 , .Xr iscsi.conf 5 , .Xr iscsid 8 .Sh HISTORY The .Nm command appeared in .Fx 10.0 . .Sh AUTHORS The .Nm utility was developed by .An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org under sponsorship from the FreeBSD Foundation. Index: head/usr.bin/iscsictl/iscsictl.c =================================================================== --- head/usr.bin/iscsictl/iscsictl.c (revision 289452) +++ head/usr.bin/iscsictl/iscsictl.c (revision 289453) @@ -1,909 +1,982 @@ /*- * Copyright (c) 2012 The FreeBSD Foundation * All rights reserved. * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iscsictl.h" struct conf * conf_new(void) { struct conf *conf; conf = calloc(1, sizeof(*conf)); if (conf == NULL) xo_err(1, "calloc"); TAILQ_INIT(&conf->conf_targets); return (conf); } struct target * target_find(struct conf *conf, const char *nickname) { struct target *targ; TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { if (targ->t_nickname != NULL && strcasecmp(targ->t_nickname, nickname) == 0) return (targ); } return (NULL); } struct target * target_new(struct conf *conf) { struct target *targ; targ = calloc(1, sizeof(*targ)); if (targ == NULL) xo_err(1, "calloc"); targ->t_conf = conf; TAILQ_INSERT_TAIL(&conf->conf_targets, targ, t_next); return (targ); } void target_delete(struct target *targ) { TAILQ_REMOVE(&targ->t_conf->conf_targets, targ, t_next); free(targ); } static char * default_initiator_name(void) { char *name; size_t namelen; int error; namelen = _POSIX_HOST_NAME_MAX + strlen(DEFAULT_IQN); name = calloc(1, namelen + 1); if (name == NULL) xo_err(1, "calloc"); strcpy(name, DEFAULT_IQN); error = gethostname(name + strlen(DEFAULT_IQN), namelen - strlen(DEFAULT_IQN)); if (error != 0) xo_err(1, "gethostname"); return (name); } static bool valid_hex(const char ch) { switch (ch) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'A': case 'b': case 'B': case 'c': case 'C': case 'd': case 'D': case 'e': case 'E': case 'f': case 'F': return (true); default: return (false); } } bool valid_iscsi_name(const char *name) { int i; if (strlen(name) >= MAX_NAME_LEN) { xo_warnx("overlong name for \"%s\"; max length allowed " "by iSCSI specification is %d characters", name, MAX_NAME_LEN); return (false); } /* * In the cases below, we don't return an error, just in case the admin * was right, and we're wrong. */ if (strncasecmp(name, "iqn.", strlen("iqn.")) == 0) { for (i = strlen("iqn."); name[i] != '\0'; i++) { /* * XXX: We should verify UTF-8 normalisation, as defined * by 3.2.6.2: iSCSI Name Encoding. */ if (isalnum(name[i])) continue; if (name[i] == '-' || name[i] == '.' || name[i] == ':') continue; xo_warnx("invalid character \"%c\" in iSCSI name " "\"%s\"; allowed characters are letters, digits, " "'-', '.', and ':'", name[i], name); break; } /* * XXX: Check more stuff: valid date and a valid reversed domain. */ } else if (strncasecmp(name, "eui.", strlen("eui.")) == 0) { if (strlen(name) != strlen("eui.") + 16) xo_warnx("invalid iSCSI name \"%s\"; the \"eui.\" " "should be followed by exactly 16 hexadecimal " "digits", name); for (i = strlen("eui."); name[i] != '\0'; i++) { if (!valid_hex(name[i])) { xo_warnx("invalid character \"%c\" in iSCSI " "name \"%s\"; allowed characters are 1-9 " "and A-F", name[i], name); break; } } } else if (strncasecmp(name, "naa.", strlen("naa.")) == 0) { if (strlen(name) > strlen("naa.") + 32) xo_warnx("invalid iSCSI name \"%s\"; the \"naa.\" " "should be followed by at most 32 hexadecimal " "digits", name); for (i = strlen("naa."); name[i] != '\0'; i++) { if (!valid_hex(name[i])) { xo_warnx("invalid character \"%c\" in ISCSI " "name \"%s\"; allowed characters are 1-9 " "and A-F", name[i], name); break; } } } else { xo_warnx("invalid iSCSI name \"%s\"; should start with " "either \".iqn\", \"eui.\", or \"naa.\"", name); } return (true); } void conf_verify(struct conf *conf) { struct target *targ; TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { assert(targ->t_nickname != NULL); if (targ->t_session_type == SESSION_TYPE_UNSPECIFIED) targ->t_session_type = SESSION_TYPE_NORMAL; if (targ->t_session_type == SESSION_TYPE_NORMAL && targ->t_name == NULL) xo_errx(1, "missing TargetName for target \"%s\"", targ->t_nickname); if (targ->t_session_type == SESSION_TYPE_DISCOVERY && targ->t_name != NULL) xo_errx(1, "cannot specify TargetName for discovery " "sessions for target \"%s\"", targ->t_nickname); if (targ->t_name != NULL) { if (valid_iscsi_name(targ->t_name) == false) xo_errx(1, "invalid target name \"%s\"", targ->t_name); } if (targ->t_protocol == PROTOCOL_UNSPECIFIED) targ->t_protocol = PROTOCOL_ISCSI; if (targ->t_address == NULL) xo_errx(1, "missing TargetAddress for target \"%s\"", targ->t_nickname); if (targ->t_initiator_name == NULL) targ->t_initiator_name = default_initiator_name(); if (valid_iscsi_name(targ->t_initiator_name) == false) xo_errx(1, "invalid initiator name \"%s\"", targ->t_initiator_name); if (targ->t_header_digest == DIGEST_UNSPECIFIED) targ->t_header_digest = DIGEST_NONE; if (targ->t_data_digest == DIGEST_UNSPECIFIED) targ->t_data_digest = DIGEST_NONE; if (targ->t_auth_method == AUTH_METHOD_UNSPECIFIED) { if (targ->t_user != NULL || targ->t_secret != NULL || targ->t_mutual_user != NULL || targ->t_mutual_secret != NULL) targ->t_auth_method = AUTH_METHOD_CHAP; else targ->t_auth_method = AUTH_METHOD_NONE; } if (targ->t_auth_method == AUTH_METHOD_CHAP) { if (targ->t_user == NULL) { xo_errx(1, "missing chapIName for target \"%s\"", targ->t_nickname); } if (targ->t_secret == NULL) xo_errx(1, "missing chapSecret for target \"%s\"", targ->t_nickname); if (targ->t_mutual_user != NULL || targ->t_mutual_secret != NULL) { if (targ->t_mutual_user == NULL) xo_errx(1, "missing tgtChapName for " "target \"%s\"", targ->t_nickname); if (targ->t_mutual_secret == NULL) xo_errx(1, "missing tgtChapSecret for " "target \"%s\"", targ->t_nickname); } } } } static void conf_from_target(struct iscsi_session_conf *conf, const struct target *targ) { memset(conf, 0, sizeof(*conf)); /* * XXX: Check bounds and return error instead of silently truncating. */ if (targ->t_initiator_name != NULL) strlcpy(conf->isc_initiator, targ->t_initiator_name, sizeof(conf->isc_initiator)); if (targ->t_initiator_address != NULL) strlcpy(conf->isc_initiator_addr, targ->t_initiator_address, sizeof(conf->isc_initiator_addr)); if (targ->t_initiator_alias != NULL) strlcpy(conf->isc_initiator_alias, targ->t_initiator_alias, sizeof(conf->isc_initiator_alias)); if (targ->t_name != NULL) strlcpy(conf->isc_target, targ->t_name, sizeof(conf->isc_target)); if (targ->t_address != NULL) strlcpy(conf->isc_target_addr, targ->t_address, sizeof(conf->isc_target_addr)); if (targ->t_user != NULL) strlcpy(conf->isc_user, targ->t_user, sizeof(conf->isc_user)); if (targ->t_secret != NULL) strlcpy(conf->isc_secret, targ->t_secret, sizeof(conf->isc_secret)); if (targ->t_mutual_user != NULL) strlcpy(conf->isc_mutual_user, targ->t_mutual_user, sizeof(conf->isc_mutual_user)); if (targ->t_mutual_secret != NULL) strlcpy(conf->isc_mutual_secret, targ->t_mutual_secret, sizeof(conf->isc_mutual_secret)); if (targ->t_session_type == SESSION_TYPE_DISCOVERY) conf->isc_discovery = 1; if (targ->t_protocol == PROTOCOL_ISER) conf->isc_iser = 1; if (targ->t_offload != NULL) strlcpy(conf->isc_offload, targ->t_offload, sizeof(conf->isc_offload)); if (targ->t_header_digest == DIGEST_CRC32C) conf->isc_header_digest = ISCSI_DIGEST_CRC32C; else conf->isc_header_digest = ISCSI_DIGEST_NONE; if (targ->t_data_digest == DIGEST_CRC32C) conf->isc_data_digest = ISCSI_DIGEST_CRC32C; else conf->isc_data_digest = ISCSI_DIGEST_NONE; } static int kernel_add(int iscsi_fd, const struct target *targ) { struct iscsi_session_add isa; int error; memset(&isa, 0, sizeof(isa)); conf_from_target(&isa.isa_conf, targ); error = ioctl(iscsi_fd, ISCSISADD, &isa); if (error != 0) xo_warn("ISCSISADD"); return (error); } static int kernel_modify(int iscsi_fd, unsigned int session_id, const struct target *targ) { struct iscsi_session_modify ism; int error; memset(&ism, 0, sizeof(ism)); ism.ism_session_id = session_id; conf_from_target(&ism.ism_conf, targ); error = ioctl(iscsi_fd, ISCSISMODIFY, &ism); if (error != 0) xo_warn("ISCSISMODIFY"); return (error); } static void kernel_modify_some(int iscsi_fd, unsigned int session_id, const char *target, const char *target_addr, const char *user, const char *secret) { struct iscsi_session_state *states = NULL; struct iscsi_session_state *state; struct iscsi_session_conf *conf; struct iscsi_session_list isl; struct iscsi_session_modify ism; unsigned int i, nentries = 1; int error; for (;;) { states = realloc(states, nentries * sizeof(struct iscsi_session_state)); if (states == NULL) xo_err(1, "realloc"); memset(&isl, 0, sizeof(isl)); isl.isl_nentries = nentries; isl.isl_pstates = states; error = ioctl(iscsi_fd, ISCSISLIST, &isl); if (error != 0 && errno == EMSGSIZE) { nentries *= 4; continue; } break; } if (error != 0) xo_errx(1, "ISCSISLIST"); for (i = 0; i < isl.isl_nentries; i++) { state = &states[i]; if (state->iss_id == session_id) break; } if (i == isl.isl_nentries) xo_errx(1, "session-id %u not found", session_id); conf = &state->iss_conf; if (target != NULL) strlcpy(conf->isc_target, target, sizeof(conf->isc_target)); if (target_addr != NULL) strlcpy(conf->isc_target_addr, target_addr, sizeof(conf->isc_target_addr)); if (user != NULL) strlcpy(conf->isc_user, user, sizeof(conf->isc_user)); if (secret != NULL) strlcpy(conf->isc_secret, secret, sizeof(conf->isc_secret)); memset(&ism, 0, sizeof(ism)); ism.ism_session_id = session_id; memcpy(&ism.ism_conf, conf, sizeof(ism.ism_conf)); error = ioctl(iscsi_fd, ISCSISMODIFY, &ism); if (error != 0) xo_warn("ISCSISMODIFY"); } static int kernel_remove(int iscsi_fd, const struct target *targ) { struct iscsi_session_remove isr; int error; memset(&isr, 0, sizeof(isr)); conf_from_target(&isr.isr_conf, targ); error = ioctl(iscsi_fd, ISCSISREMOVE, &isr); if (error != 0) xo_warn("ISCSISREMOVE"); return (error); } /* * XXX: Add filtering. */ static int kernel_list(int iscsi_fd, const struct target *targ __unused, int verbose) { struct iscsi_session_state *states = NULL; const struct iscsi_session_state *state; const struct iscsi_session_conf *conf; struct iscsi_session_list isl; unsigned int i, nentries = 1; int error; for (;;) { states = realloc(states, nentries * sizeof(struct iscsi_session_state)); if (states == NULL) xo_err(1, "realloc"); memset(&isl, 0, sizeof(isl)); isl.isl_nentries = nentries; isl.isl_pstates = states; error = ioctl(iscsi_fd, ISCSISLIST, &isl); if (error != 0 && errno == EMSGSIZE) { nentries *= 4; continue; } break; } if (error != 0) { xo_warn("ISCSISLIST"); return (error); } if (verbose != 0) { xo_open_list("session"); for (i = 0; i < isl.isl_nentries; i++) { state = &states[i]; conf = &state->iss_conf; xo_open_instance("session"); /* * Display-only modifier as this information * is also present within the 'session' container */ xo_emit("{L:/%-18s}{V:sessionId/%u}\n", "Session ID:", state->iss_id); xo_open_container("initiator"); xo_emit("{L:/%-18s}{V:name/%s}\n", "Initiator name:", conf->isc_initiator); xo_emit("{L:/%-18s}{V:portal/%s}\n", "Initiator portal:", conf->isc_initiator_addr); xo_emit("{L:/%-18s}{V:alias/%s}\n", "Initiator alias:", conf->isc_initiator_alias); xo_close_container("initiator"); xo_open_container("target"); xo_emit("{L:/%-18s}{V:name/%s}\n", "Target name:", conf->isc_target); xo_emit("{L:/%-18s}{V:portal/%s}\n", "Target portal:", conf->isc_target_addr); xo_emit("{L:/%-18s}{V:alias/%s}\n", "Target alias:", state->iss_target_alias); xo_close_container("target"); xo_open_container("auth"); xo_emit("{L:/%-18s}{V:user/%s}\n", "User:", conf->isc_user); xo_emit("{L:/%-18s}{V:secret/%s}\n", "Secret:", conf->isc_secret); xo_emit("{L:/%-18s}{V:mutualUser/%s}\n", "Mutual user:", conf->isc_mutual_user); xo_emit("{L:/%-18s}{V:mutualSecret/%s}\n", "Mutual secret:", conf->isc_mutual_secret); xo_close_container("auth"); xo_emit("{L:/%-18s}{V:type/%s}\n", "Session type:", conf->isc_discovery ? "Discovery" : "Normal"); xo_emit("{L:/%-18s}{V:state/%s}\n", "Session state:", state->iss_connected ? "Connected" : "Disconnected"); xo_emit("{L:/%-18s}{V:failureReason/%s}\n", "Failure reason:", state->iss_reason); xo_emit("{L:/%-18s}{V:headerDigest/%s}\n", "Header digest:", state->iss_header_digest == ISCSI_DIGEST_CRC32C ? "CRC32C" : "None"); xo_emit("{L:/%-18s}{V:dataDigest/%s}\n", "Data digest:", state->iss_data_digest == ISCSI_DIGEST_CRC32C ? "CRC32C" : "None"); xo_emit("{L:/%-18s}{V:dataSegmentLen/%d}\n", "DataSegmentLen:", state->iss_max_data_segment_length); xo_emit("{L:/%-18s}{V:immediateData/%s}\n", "ImmediateData:", state->iss_immediate_data ? "Yes" : "No"); xo_emit("{L:/%-18s}{V:iSER/%s}\n", "iSER (RDMA):", conf->isc_iser ? "Yes" : "No"); xo_emit("{L:/%-18s}{V:offloadDriver/%s}\n", "Offload driver:", state->iss_offload); xo_emit("{L:/%-18s}", "Device nodes:"); print_periphs(state->iss_id); xo_emit("\n\n"); xo_close_instance("session"); } xo_close_list("session"); } else { xo_emit("{T:/%-36s} {T:/%-16s} {T:/%s}\n", "Target name", "Target portal", "State"); if (isl.isl_nentries != 0) xo_open_list("session"); for (i = 0; i < isl.isl_nentries; i++) { state = &states[i]; conf = &state->iss_conf; xo_open_instance("session"); xo_emit("{V:name/%-36s/%s} {V:portal/%-16s/%s} ", conf->isc_target, conf->isc_target_addr); if (state->iss_reason[0] != '\0') { xo_emit("{V:state/%s}\n", state->iss_reason); } else { if (conf->isc_discovery) { xo_emit("{V:state}\n", "Discovery"); } else if (state->iss_connected) { xo_emit("{V:state}: ", "Connected"); print_periphs(state->iss_id); xo_emit("\n"); } else { xo_emit("{V:state}\n", "Disconnected"); } } xo_close_instance("session"); } if (isl.isl_nentries != 0) xo_close_list("session"); } return (0); } +static int +kernel_wait(int iscsi_fd, int timeout) +{ + struct iscsi_session_state *states = NULL; + const struct iscsi_session_state *state; + const struct iscsi_session_conf *conf; + struct iscsi_session_list isl; + unsigned int i, nentries = 1; + bool all_connected; + int error; + + for (;;) { + for (;;) { + states = realloc(states, + nentries * sizeof(struct iscsi_session_state)); + if (states == NULL) + xo_err(1, "realloc"); + + memset(&isl, 0, sizeof(isl)); + isl.isl_nentries = nentries; + isl.isl_pstates = states; + + error = ioctl(iscsi_fd, ISCSISLIST, &isl); + if (error != 0 && errno == EMSGSIZE) { + nentries *= 4; + continue; + } + break; + } + if (error != 0) { + xo_warn("ISCSISLIST"); + return (error); + } + + all_connected = true; + for (i = 0; i < isl.isl_nentries; i++) { + state = &states[i]; + conf = &state->iss_conf; + + if (!state->iss_connected) { + all_connected = false; + break; + } + } + + if (all_connected) + return (0); + + sleep(1); + + if (timeout > 0) { + timeout--; + if (timeout == 0) + return (1); + } + } +} + static void usage(void) { fprintf(stderr, "usage: iscsictl -A -p portal -t target " - "[-u user -s secret]\n"); + "[-u user -s secret] [-w timeout]\n"); fprintf(stderr, " iscsictl -A -d discovery-host " "[-u user -s secret]\n"); fprintf(stderr, " iscsictl -A -a [-c path]\n"); fprintf(stderr, " iscsictl -A -n nickname [-c path]\n"); fprintf(stderr, " iscsictl -M -i session-id [-p portal] " "[-t target] [-u user] [-s secret]\n"); fprintf(stderr, " iscsictl -M -i session-id -n nickname " "[-c path]\n"); fprintf(stderr, " iscsictl -R [-p portal] [-t target]\n"); fprintf(stderr, " iscsictl -R -a\n"); fprintf(stderr, " iscsictl -R -n nickname [-c path]\n"); - fprintf(stderr, " iscsictl -L [-v]\n"); + fprintf(stderr, " iscsictl -L [-v] [-w timeout]\n"); exit(1); } char * checked_strdup(const char *s) { char *c; c = strdup(s); if (c == NULL) xo_err(1, "strdup"); return (c); } int main(int argc, char **argv) { int Aflag = 0, Mflag = 0, Rflag = 0, Lflag = 0, aflag = 0, vflag = 0; const char *conf_path = DEFAULT_CONFIG_PATH; char *nickname = NULL, *discovery_host = NULL, *portal = NULL, *target = NULL, *user = NULL, *secret = NULL; + int timeout = -1; long long session_id = -1; char *end; int ch, error, iscsi_fd, retval, saved_errno; int failed = 0; struct conf *conf; struct target *targ; argc = xo_parse_args(argc, argv); xo_open_container("iscsictl"); - while ((ch = getopt(argc, argv, "AMRLac:d:i:n:p:t:u:s:v")) != -1) { + while ((ch = getopt(argc, argv, "AMRLac:d:i:n:p:t:u:s:vw:")) != -1) { switch (ch) { case 'A': Aflag = 1; break; case 'M': Mflag = 1; break; case 'R': Rflag = 1; break; case 'L': Lflag = 1; break; case 'a': aflag = 1; break; case 'c': conf_path = optarg; break; case 'd': discovery_host = optarg; break; case 'i': session_id = strtol(optarg, &end, 10); if ((size_t)(end - optarg) != strlen(optarg)) xo_errx(1, "trailing characters after session-id"); if (session_id < 0) xo_errx(1, "session-id cannot be negative"); if (session_id > UINT_MAX) xo_errx(1, "session-id cannot be greater than %u", UINT_MAX); break; case 'n': nickname = optarg; break; case 'p': portal = optarg; break; case 't': target = optarg; break; case 'u': user = optarg; break; case 's': secret = optarg; break; case 'v': vflag = 1; break; + case 'w': + timeout = strtol(optarg, &end, 10); + if ((size_t)(end - optarg) != strlen(optarg)) + xo_errx(1, "trailing characters after timeout"); + if (timeout < 0) + xo_errx(1, "timeout cannot be negative"); + break; case '?': default: usage(); } } argc -= optind; if (argc != 0) usage(); if (Aflag + Mflag + Rflag + Lflag == 0) Lflag = 1; if (Aflag + Mflag + Rflag + Lflag > 1) xo_errx(1, "at most one of -A, -M, -R, or -L may be specified"); /* * Note that we ignore unneccessary/inapplicable "-c" flag; so that * people can do something like "alias ISCSICTL="iscsictl -c path" * in shell scripts. */ if (Aflag != 0) { if (aflag != 0) { if (portal != NULL) xo_errx(1, "-a and -p and mutually exclusive"); if (target != NULL) xo_errx(1, "-a and -t and mutually exclusive"); if (user != NULL) xo_errx(1, "-a and -u and mutually exclusive"); if (secret != NULL) xo_errx(1, "-a and -s and mutually exclusive"); if (nickname != NULL) xo_errx(1, "-a and -n and mutually exclusive"); if (discovery_host != NULL) xo_errx(1, "-a and -d and mutually exclusive"); } else if (nickname != NULL) { if (portal != NULL) xo_errx(1, "-n and -p and mutually exclusive"); if (target != NULL) xo_errx(1, "-n and -t and mutually exclusive"); if (user != NULL) xo_errx(1, "-n and -u and mutually exclusive"); if (secret != NULL) xo_errx(1, "-n and -s and mutually exclusive"); if (discovery_host != NULL) xo_errx(1, "-n and -d and mutually exclusive"); } else if (discovery_host != NULL) { if (portal != NULL) xo_errx(1, "-d and -p and mutually exclusive"); if (target != NULL) xo_errx(1, "-d and -t and mutually exclusive"); } else { if (target == NULL && portal == NULL) xo_errx(1, "must specify -a, -n or -t/-p"); if (target != NULL && portal == NULL) xo_errx(1, "-t must always be used with -p"); if (portal != NULL && target == NULL) xo_errx(1, "-p must always be used with -t"); } if (user != NULL && secret == NULL) xo_errx(1, "-u must always be used with -s"); if (secret != NULL && user == NULL) xo_errx(1, "-s must always be used with -u"); if (session_id != -1) xo_errx(1, "-i cannot be used with -A"); if (vflag != 0) xo_errx(1, "-v cannot be used with -A"); } else if (Mflag != 0) { if (session_id == -1) xo_errx(1, "-M requires -i"); if (discovery_host != NULL) xo_errx(1, "-M and -d are mutually exclusive"); if (aflag != 0) xo_errx(1, "-M and -a are mutually exclusive"); if (nickname != NULL) { if (portal != NULL) xo_errx(1, "-n and -p and mutually exclusive"); if (target != NULL) xo_errx(1, "-n and -t and mutually exclusive"); if (user != NULL) xo_errx(1, "-n and -u and mutually exclusive"); if (secret != NULL) xo_errx(1, "-n and -s and mutually exclusive"); } if (vflag != 0) xo_errx(1, "-v cannot be used with -M"); + if (timeout != -1) + xo_errx(1, "-w cannot be used with -M"); } else if (Rflag != 0) { if (user != NULL) xo_errx(1, "-R and -u are mutually exclusive"); if (secret != NULL) xo_errx(1, "-R and -s are mutually exclusive"); if (discovery_host != NULL) xo_errx(1, "-R and -d are mutually exclusive"); if (aflag != 0) { if (portal != NULL) xo_errx(1, "-a and -p and mutually exclusive"); if (target != NULL) xo_errx(1, "-a and -t and mutually exclusive"); if (nickname != NULL) xo_errx(1, "-a and -n and mutually exclusive"); } else if (nickname != NULL) { if (portal != NULL) xo_errx(1, "-n and -p and mutually exclusive"); if (target != NULL) xo_errx(1, "-n and -t and mutually exclusive"); } else if (target == NULL && portal == NULL) { xo_errx(1, "must specify either -a, -n, -t, or -p"); } if (session_id != -1) xo_errx(1, "-i cannot be used with -R"); if (vflag != 0) xo_errx(1, "-v cannot be used with -R"); + if (timeout != -1) + xo_errx(1, "-w cannot be used with -R"); } else { assert(Lflag != 0); if (portal != NULL) xo_errx(1, "-L and -p and mutually exclusive"); if (target != NULL) xo_errx(1, "-L and -t and mutually exclusive"); if (user != NULL) xo_errx(1, "-L and -u and mutually exclusive"); if (secret != NULL) xo_errx(1, "-L and -s and mutually exclusive"); if (nickname != NULL) xo_errx(1, "-L and -n and mutually exclusive"); if (discovery_host != NULL) xo_errx(1, "-L and -d and mutually exclusive"); if (session_id != -1) xo_errx(1, "-i cannot be used with -L"); } iscsi_fd = open(ISCSI_PATH, O_RDWR); if (iscsi_fd < 0 && errno == ENOENT) { saved_errno = errno; retval = kldload("iscsi"); if (retval != -1) iscsi_fd = open(ISCSI_PATH, O_RDWR); else errno = saved_errno; } if (iscsi_fd < 0) xo_err(1, "failed to open %s", ISCSI_PATH); if (Aflag != 0 && aflag != 0) { conf = conf_new_from_file(conf_path); TAILQ_FOREACH(targ, &conf->conf_targets, t_next) failed += kernel_add(iscsi_fd, targ); } else if (nickname != NULL) { conf = conf_new_from_file(conf_path); targ = target_find(conf, nickname); if (targ == NULL) xo_errx(1, "target %s not found in %s", nickname, conf_path); if (Aflag != 0) failed += kernel_add(iscsi_fd, targ); else if (Mflag != 0) failed += kernel_modify(iscsi_fd, session_id, targ); else if (Rflag != 0) failed += kernel_remove(iscsi_fd, targ); else failed += kernel_list(iscsi_fd, targ, vflag); } else if (Mflag != 0) { kernel_modify_some(iscsi_fd, session_id, target, portal, user, secret); } else { if (Aflag != 0 && target != NULL) { if (valid_iscsi_name(target) == false) xo_errx(1, "invalid target name \"%s\"", target); } conf = conf_new(); targ = target_new(conf); targ->t_initiator_name = default_initiator_name(); targ->t_header_digest = DIGEST_NONE; targ->t_data_digest = DIGEST_NONE; targ->t_name = target; if (discovery_host != NULL) { targ->t_session_type = SESSION_TYPE_DISCOVERY; targ->t_address = discovery_host; } else { targ->t_session_type = SESSION_TYPE_NORMAL; targ->t_address = portal; } targ->t_user = user; targ->t_secret = secret; if (Aflag != 0) failed += kernel_add(iscsi_fd, targ); else if (Rflag != 0) failed += kernel_remove(iscsi_fd, targ); else failed += kernel_list(iscsi_fd, targ, vflag); } + + if (timeout != -1) + failed += kernel_wait(iscsi_fd, timeout); error = close(iscsi_fd); if (error != 0) xo_err(1, "close"); if (failed > 0) return (1); xo_close_container("iscsictl"); xo_finish(); return (0); }