root/sbin/iscsictl/iscsic_parse.c
/*      $NetBSD: iscsic_parse.c,v 1.5 2023/11/25 08:06:02 mlelstv Exp $ */

/*-
 * Copyright (c) 2005,2006,2011 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Wasabi Systems, Inc.
 *
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 "iscsic_globals.h"

#include <ctype.h>
#include <assert.h>

/*
 * get_address:
 *    Get an address specification that may include port and group tag.
 *
 *    Parameter:
 *       portal   The portal address
 *       str      The parameter string to scan
 *
 *    Aborts app on error.
 */

STATIC void
get_address(iscsi_portal_address_t * portal, char *str, char *arg)
{
        char *sp;
        int val;

        if (!str || !*str)
                arg_error(arg, "Address is missing");

        /* Parse and strip trailing group tag */
        sp = strrchr(str, ',');
        if (sp != NULL) {
                if (sscanf(sp + 1, "%d", &val) != 1)
                        arg_error(arg, "Bad address format: Expected group tag");
                if (val < 0 || val > 0xffff)
                        arg_error(arg, "Bad address format: Group tag out of range");
                portal->group_tag = (uint16_t) val;
                *sp = '\0';
        }

        /* Skip over bracketed IPv6 address */
        sp = strchr(str, ']');
        if (sp != NULL)
                sp++;
        else
                sp = str;

        /* Parse and strip trailing port number */
        sp = strchr(sp, ':');
        if (sp != NULL) {
                if (strchr(sp + 1, ':') != NULL) {
                        /*
                         *  If there's a second colon, assume
                         *  it's an unbracketed IPv6 address
                         */
                        portal->port = 0;
                } else {
                        if (sscanf(sp + 1, "%d", &val) != 1)
                                arg_error(arg, "Bad address format: Expected port number");
                        if (val < 0 || val > 0xffff)
                                arg_error(arg, "Bad address format: Port number ut  of range");
                        portal->port = (uint16_t) val;
                        *sp = '\0';
                }
        }

        /* Remove brackets */
        if (*str == '[') {
                sp = strchr(str, ']');
                if (sp != NULL && !*(sp+1)) {
                        str = str + 1;
                        *sp = '\0';
                }
        }

        /*
         * only check length, don't verify correct format
         * (too many possibilities)
         */
        if (strlen(str) >= sizeof(portal->address))
                arg_error(arg, "Bad address format: Address string too long");

        strlcpy((char *)portal->address, str, sizeof(portal->address));
}



/*
 * get_short_int:
 *    Get a short integer.
 *
 *    Parameter:
 *       sp       The parameter string to scan
 *       arg      The associated option argument (for error message)
 *       name     The argument name
 *
 *    Returns given integer, aborts app on error.
 */

STATIC uint16_t
get_short_int(char *sp, char *arg, const char *name)
{
        int val;

        if (!sp || !*sp)
                arg_error(arg, "%s is missing", name);

        if (!sscanf(sp, "%d", &val))
                arg_error(arg, "Expected integer %s", name);
        if (val < 0 || val > 0xffff)
                arg_error(arg, "%s out of range", name);

        return (uint16_t) val;
}


/*
 * get_dsl:
 *    Get MaxRecvDataSegmentLength
 *
 *    Parameter:
 *       sp       The parameter string to scan
 *       arg      The associated option argument (for error message)
 *
 *    Returns given integer, aborts app on error.
 */

STATIC uint32_t
get_dsl(char *sp, char *arg)
{
        int val;

        if (!sp || !*sp)
                arg_error(arg, "Missing MaxRecvDataSegmentLength");
        if (!sscanf(sp, "%d", &val))
                arg_error(arg, "Integer MaxRecvDataSegmentLength expected");
        if (val < 512 || val > 0xffffff)
                arg_error(arg, "MaxRecvDataSegmentLength out of range");

        return (uint32_t) val;
}


/*
 * get_str:
 *    Get a string.
 *
 *    Parameter:
 *       dest     The destination string
 *       sp       The parameter string to scan
 *       arg      The associated option argument (for error message)
 *       name     The argument name
 *
 *    Aborts app on error.
 */

STATIC void
get_str(char *dest, char *sp, char *arg, const char *name)
{

        if (!sp || !*sp)
                arg_error(arg, "%s is missing", name);
        if (strlen(sp) >= ISCSI_STRING_LENGTH)
                arg_error(arg, "%s is too long", name);

        strlcpy(dest, sp, ISCSI_STRING_LENGTH);
}

/*
 * cl_get_target:
 *    Get a target address specification that may include name, address, port,
 *    and group tag, with address/port/tag possibly repeated.
 *
 *    Parameter:
 *       ptarg       pointer to hold the resulting add target request parameter
 *       argc, argv  program parameters (shifted)
 *       nreq        target name is required if TRUE
 *
 *    Returns:    0 if there is no target, else the size of the allocated
 *                  request.
 *                Aborts app on bad parameter or mem allocation error.
 */

int
cl_get_target(iscsid_add_target_req_t ** ptarg, int argc, char **argv, int nreq)
{
        iscsid_add_target_req_t *targ;
        char *sp;
        size_t num, len, name;
        int i, p;

        /* count number of addresses first, so we know how much memory to allocate */
        for (i = (int)(num = name = 0); i < argc; i++) {
                if (!argv[i] || argv[i][0] != '-')
                        continue;
                if (argv[i][1] == 'a')
                        num++;
                if (argv[i][1] == 'n')
                        name++;
        }

        if (!name && nreq)
                return 0;

        len = sizeof(iscsid_add_target_req_t) +
                num * sizeof(iscsi_portal_address_t);

        if (NULL == (targ = calloc(1, len)))
                gen_error("Can't allocate %zu bytes of memory", len);

        *ptarg = targ;
        p = -1;

        for (i = 0; i < argc; i++) {
                if (!argv[i] || argv[i][0] != '-')
                        continue;

                sp = (argv[i][2]) ? &argv[i][2] : ((i + 1 < argc) ? argv[i + 1] : NULL);

                switch (argv[i][1]) {
                case 'n':                               /* target name */
                        get_str((char *)targ->TargetName, sp, argv[i], "Target name");
                        break;

                case 'a':                               /* target address */
                        get_address(&targ->portal[++p], sp, argv[i]);
                        break;

                case 'p':                               /* port */
                        assert(p >= 0);
                        targ->portal[p].port = get_short_int(sp, argv[i], "Port");
                        break;

                case 'g':                               /* group tag */
                        assert(p >= 0);
                        targ->portal[p].group_tag = get_short_int(sp, argv[i],
                                                                                                                "Group tag");
                        break;

                default:
                        continue;
                }
                if (!argv[i][2])
                        argv[i + 1] = NULL;

                argv[i] = NULL;
        }
        targ->num_portals = p + 1;

        return (int)len;
}


/*
 * cl_get_isns:
 *    Get an iSNS server address specification that may include name, address
 *    and port.
 *
 *    Parameter:
 *       srv         add_isns_server request parameter
 *       argc, argv  program parameters (shifted)
 *
 *    Returns:    0 on error, 1 if OK.
 */

int
cl_get_isns(iscsid_add_isns_server_req_t * srv, int argc, char **argv)
{
        iscsi_portal_address_t addr;
        char *sp;
        int i, found;

        (void) memset(&addr, 0x0, sizeof(addr));
        found = FALSE;

        for (i = 0; i < argc; i++) {
                if (!argv[i] || argv[i][0] != '-')
                        continue;

                sp = (argv[i][2]) ? &argv[i][2] : ((i + 1 < argc) ? argv[i + 1] : NULL);

                switch (argv[i][1]) {
                case 'N':                               /* symbolic name */
                        get_str((char *)srv->name, sp, argv[i], "Server name");
                        break;

                case 'a':                               /* target address */
                        get_address(&addr, sp, argv[i]);
                        found = TRUE;
                        break;

                case 'p':                               /* port */
                        addr.port = get_short_int(sp, argv[i], "Port");
                        break;

                default:
                        continue;
                }
                if (!argv[i][2]) {
                        argv[i + 1] = NULL;
                }
                argv[i] = NULL;
        }

        strlcpy((char *)srv->address, (char *)addr.address, sizeof(srv->address));
        srv->port = addr.port;

        return found;
}


/*
 * cl_get_auth_opts:
 *    Get authentication options.
 *
 *    Parameter:
 *          auth        authentication parameters
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    0 if there are no authorization options, 1 otherwise.
 *                Aborts app on bad parameter.
 */

int
cl_get_auth_opts(iscsid_set_target_authentication_req_t *auth,
                                 int argc, char **argv)
{
        int n, i, found;
        char *sp;

        found = FALSE;
        memset(auth, 0, sizeof(*auth));

        for (i = 0; i < argc; i++) {
                if (!argv[i] || argv[i][0] != '-') {
                        continue;
                }
                sp = (argv[i][2]) ? &argv[i][2] : ((i + 1 < argc) ? argv[i + 1] : NULL);

                switch (argv[i][1]) {
                case 't':                               /* authentication type */
                        if (!sp || !*sp)
                                arg_error(argv[i], "Missing authentication type");
                        n = 0;
                        while (*sp) {
                                switch (*sp) {
                                case 'n':               /* no authentication */
                                        auth->auth_info.auth_type[n] = ISCSI_AUTH_None;
                                        break;
                                case 'c':               /* CHAP authentication */
                                        auth->auth_info.auth_type[n] = ISCSI_AUTH_CHAP;
                                        break;
                                case 'C':               /* Mutual CHAP authentication */
                                        auth->auth_info.auth_type[n] = ISCSI_AUTH_CHAP;
                                        auth->auth_info.mutual_auth = 1;
                                        break;
                                default:
                                        arg_error(argv[i], "Bad authentication type '%c'", *sp);
                                }
                                sp++;
                                n++;
                        }
                        auth->auth_info.auth_number = n;
                        break;

                case 'u':                               /* user name */
                        get_str((char *)auth->user_name, sp, argv[i], "User name");
                        break;

                case 's':                               /* secret */
                        get_str((char *)auth->password, sp, argv[i], "Secret");
                        break;

                case 'S':                               /* target secret */
                        get_str((char *)auth->target_password, sp, argv[i], "Target secret");
                        break;

                default:
                        continue;
                }
                if (!argv[i][2])
                        argv[i + 1] = NULL;

                argv[i] = NULL;
                found = TRUE;
        }
        return found;
}


/*
 * cl_get_target_opts:
 *    Get session/connection options.
 *
 *    Parameter:
 *          opt         target options
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    0 if there are no target options, 1 otherwise.
 *                Aborts app on bad parameter.
 */

int
cl_get_target_opts(iscsid_get_set_target_options_t * opt, int argc, char **argv)
{
        int i, found;
        char *sp;

        found = FALSE;
        memset(opt, 0, sizeof(*opt));

        for (i = 0; i < argc; i++) {
                if (!argv[i] || argv[i][0] != '-')
                        continue;

                sp = (argv[i][2]) ? &argv[i][2] : ((i + 1 < argc) ? argv[i + 1] : NULL);

                switch (argv[i][1]) {
                case 'h':                               /* Header Digest */
                        opt->HeaderDigest = ISCSI_DIGEST_CRC32C;
                        opt->is_present.HeaderDigest = 1;
                        break;

                case 'd':                               /* Data Digest */
                        opt->DataDigest = ISCSI_DIGEST_CRC32C;
                        opt->is_present.DataDigest = 1;
                        break;

                case 'w':                               /* Time 2 Wait */
                        opt->DefaultTime2Wait = get_short_int(sp, argv[i], "Time to wait");
                        opt->is_present.DefaultTime2Wait = 1;
                        if (!argv[i][2])
                                argv[i + 1] = NULL;
                        break;

                case 'r':                               /* Time 2 Retain */
                        opt->DefaultTime2Retain = get_short_int(sp, argv[i],
                                                                                                        "Time to retain");
                        opt->is_present.DefaultTime2Retain = 1;
                        if (!argv[i][2])
                                argv[i + 1] = NULL;
                        break;

                case 'e':                               /* Error Recovery Level */
                        opt->ErrorRecoveryLevel = get_short_int(sp, argv[i],
                                                                                                        "ErrorRecoveryLevel");
                        opt->is_present.ErrorRecoveryLevel = 1;
                        if (!argv[i][2])
                                argv[i + 1] = NULL;
                        break;

                case 'l':                               /* Data Segment Length */
                        opt->MaxRecvDataSegmentLength = get_dsl(sp, argv[i]);
                        opt->is_present.MaxRecvDataSegmentLength = 1;
                        if (!argv[i][2])
                                argv[i + 1] = NULL;
                        break;

                default:
                        continue;
                }
                argv[i] = NULL;
                found = TRUE;
        }
        return found;
}


/*
 * cl_get_portal:
 *    Get a portal address specification that may include address, port,
 *    and group tag, plus portal options.
 *
 *    Parameter:
 *          port        add portal request parameter
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    FALSE if there is no portal, else TRUE.
 *                Aborts app on bad parameter or mem allocation error.
 */

int
cl_get_portal(iscsid_add_portal_req_t * port, int argc, char **argv)
{
        char *sp;
        int i, found;
        iscsid_portal_options_t *opt = &port->options;

        found = FALSE;
        memset(port, 0, sizeof(*port));

        for (i = 0; i < argc; i++) {
                if (!argv[i] || argv[i][0] != '-')
                        continue;

                sp = (argv[i][2]) ? &argv[i][2] : ((i + 1 < argc) ? argv[i + 1] : NULL);

                switch (argv[i][1]) {
                case 'a':                               /* target address */
                        get_address(&port->portal, sp, argv[i]);
                        found = TRUE;
                        break;

                case 'p':                               /* port */
                        port->portal.port = get_short_int(sp, argv[i], "Port");
                        break;

                case 'g':                               /* group tag */
                        port->portal.group_tag = get_short_int(sp, argv[i], "Group tag");
                        break;

                case 'h':                               /* Header Digest */
                        opt->HeaderDigest = ISCSI_DIGEST_CRC32C;
                        opt->is_present.HeaderDigest = 1;
                        break;

                case 'd':                               /* Data Digest */
                        opt->DataDigest = ISCSI_DIGEST_CRC32C;
                        opt->is_present.DataDigest = 1;
                        break;

                case 'l':                               /* Data Segment Length */
                        opt->MaxRecvDataSegmentLength = get_dsl(sp, argv[i]);
                        opt->is_present.MaxRecvDataSegmentLength = 1;
                        if (!argv[i][2])
                                argv[i + 1] = NULL;
                        break;

                default:
                        continue;
                }
                if (!argv[i][2])
                        argv[i + 1] = NULL;

                argv[i] = NULL;
        }
        return found;
}


/*
 * cl_get_id:
 *    Get an identifier (symbolic or numeric)
 *
 *    Parameter:
 *          ident       the parameter identifier character
 *          sid         the ID
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    0 if there is no ID, 1 otherwise.
 *                Aborts app on bad parameter.
 */

int
cl_get_id(char ident, iscsid_sym_id_t * sid, int argc, char **argv)
{
        int i, found;
        char *sp;

        found = FALSE;
        memset(sid, 0, sizeof(*sid));

        for (i = 0; i < argc && !found; i++) {
                if (!argv[i] || argv[i][0] != '-')
                        continue;

                if (argv[i][1] == ident) {
                        sp = (argv[i][2]) ? &argv[i][2] :
                                ((i + 1 < argc) ? argv[i + 1] : NULL);

                        if (!sp || !*sp)
                                arg_error(argv[i], "Missing ID");
                        if (strlen(sp) >= ISCSI_STRING_LENGTH)
                                arg_error(argv[i], "ID String too long");
                        if (!sscanf(sp, "%d", &sid->id))
                                strlcpy((char *)sid->name, sp, sizeof(sid->name));
                        else if (!sid->id)
                                arg_error(argv[i], "Invalid ID");

                        if (!argv[i][2])
                                argv[i + 1] = NULL;

                        argv[i] = NULL;
                        found = TRUE;
                }
        }
        return found;
}


/*
 * cl_get_symname:
 *    Get a symbolic name
 *
 *    Parameter:
 *          sn          the name
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    0 if there is no symbolic name, 1 otherwise.
 *                Aborts app on bad parameter.
 */

int
cl_get_symname(uint8_t * sn, int argc, char **argv)
{
        int i, found;
        char *sp;

        found = FALSE;
        *sn = '\0';

        for (i = 0; i < argc && !found; i++) {
                if (!argv[i] || argv[i][0] != '-')
                        continue;

                if (argv[i][1] == 'N') {
                        sp = (argv[i][2]) ? &argv[i][2]
                                : ((i + 1 < argc) ? argv[i + 1] : NULL);

                        if (!sp || !*sp)
                                arg_error(argv[i], "Symbolic name missing");
                        if (isdigit((unsigned char)*sp))
                                arg_error(argv[i], "Symbolic name must not be numeric");
                        if (strlen(sp) >= ISCSI_STRING_LENGTH)
                                arg_error(argv[i], "Symbolic name too long");

                        strlcpy((char *)sn, sp, ISCSI_STRING_LENGTH);

                        if (!argv[i][2])
                                argv[i + 1] = NULL;

                        argv[i] = NULL;
                        found = TRUE;
                }
        }
        return found;
}


/*
 * cl_get_string:
 *    Get a string value
 *
 *    Parameter:
 *          ident       the parameter identifier character
 *          pstr        the result string
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    0 if there is no string, 1 otherwise.
 *                Aborts app on bad parameter.
 */

int
cl_get_string(char ident, char *pstr, int argc, char **argv)
{
        int i, found;
        char *sp;

        found = FALSE;
        *pstr = '\0';

        for (i = 0; i < argc && !found; i++) {
                if (!argv[i] || argv[i][0] != '-')
                        continue;

                if (argv[i][1] == ident) {
                        sp = (argv[i][2]) ? &argv[i][2]
                                : ((i + 1 < argc) ? argv[i + 1] : NULL);

                        get_str(pstr, sp, argv[i], "String");

                        if (!argv[i][2])
                                argv[i + 1] = NULL;

                        argv[i] = NULL;
                        found = TRUE;
                }
        }
        return found;
}


/*
 * cl_get_opt:
 *    Get an option with no value
 *
 *    Parameter:
 *          ident       the parameter identifier character
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    0 if the option was not found, 1 otherwise.
 *                Aborts app on bad parameter.
 */

int
cl_get_opt(char ident, int argc, char **argv)
{
        int i, found;

        found = FALSE;

        for (i = 0; i < argc && !found; i++) {
                if (!argv[i] || argv[i][0] != '-')
                        continue;

                if (argv[i][1] == ident) {
                        argv[i] = NULL;
                        found = TRUE;
                }
        }
        return found;
}


/*
 * cl_get_char:
 *    Get an option with a character value
 *
 *    Parameter:
 *          ident       the parameter identifier character
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    The option character (0 if not found).
 *                Aborts app on bad parameter.
 */

char
cl_get_char(char ident, int argc, char **argv)
{
        int i, found;
        char *sp;
        char ch = 0;

        found = FALSE;

        for (i = 0; i < argc && !found; i++) {
                if (!argv[i] || argv[i][0] != '-')
                        continue;

                if (argv[i][1] == ident) {
                        sp = (argv[i][2]) ? &argv[i][2]
                                : ((i + 1 < argc) ? argv[i + 1] : NULL);

                        if (!sp || !*sp)
                                arg_error(argv[i], "Option character missing");
                        if (strlen(sp) > 1)
                                arg_error(argv[i], "Option invalid");
                        ch = *sp;

                        if (!argv[i][2])
                                argv[i + 1] = NULL;

                        argv[i] = NULL;
                        found = TRUE;
                }
        }

        return ch;
}


/*
 * cl_get_int:
 *    Get an option with an integer value
 *
 *    Parameter:
 *          ident       the parameter identifier character
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    The option value (0 if not found).
 *                Aborts app on bad parameter.
 */

int
cl_get_int(char ident, int argc, char **argv)
{
        int i, found;
        char *sp;
        int val = 0;

        found = FALSE;

        for (i = 0; i < argc && !found; i++) {
                if (!argv[i] || argv[i][0] != '-')
                        continue;

                if (argv[i][1] == ident) {
                        sp = (argv[i][2]) ? &argv[i][2]
                                : ((i + 1 < argc) ? argv[i + 1] : NULL);

                        if (!sp || !*sp)
                                arg_error(argv[i], "Option value missing");
                        if (!sscanf(sp, "%i", &val))
                                arg_error(argv[i], "Integer expected");

                        if (!argv[i][2])
                                argv[i + 1] = NULL;

                        argv[i] = NULL;
                        found = TRUE;
                }
        }

        return val;
}


/*
 * cl_get_uint:
 *    Get an option with a positive integer value
 *
 *    Parameter:
 *          ident       the parameter identifier character
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    The option value (-1 if not found).
 *                Aborts app on bad parameter.
 */

#if 0
int
cl_get_uint(char ident, int argc, char **argv)
{
        int i, found;
        char *sp;
        int val = -1;

        found = FALSE;

        for (i = 0; i < argc && !found; i++) {
                if (!argv[i] || argv[i][0] != '-')
                        continue;

                if (argv[i][1] == ident) {
                        sp = (argv[i][2]) ? &argv[i][2]
                                : ((i + 1 < argc) ? argv[i + 1] : NULL);

                        if (!sp || !*sp)
                                arg_error(argv[i], "Option value missing");
                        if (!sscanf(sp, "%i", &val))
                                arg_error(argv[i], "Positive integer expected");

                        if (!argv[i][2])
                                argv[i + 1] = NULL;

                        argv[i] = NULL;
                        found = TRUE;
                }
        }

        return val;
}
#endif


/*
 * cl_get_longlong:
 *    Get an option with a 64-bit value
 *
 *    Parameter:
 *          ident       the parameter identifier character
 *          argc, argv  program parameters (shifted)
 *
 *    Returns:    The option value (0 if not found).
 *                Aborts app on bad parameter.
 */

uint64_t
cl_get_longlong(char ident, int argc, char **argv)
{
        int i, found;
        char *sp;
        uint64_t val = 0;

        found = FALSE;

        for (i = 0; i < argc && !found; i++) {
                if (!argv[i] || argv[i][0] != '-')
                        continue;

                if (argv[i][1] == ident) {
                        sp = (argv[i][2]) ? &argv[i][2]
                                : ((i + 1 < argc) ? argv[i + 1] : NULL);

                        if (!sp || !*sp)
                                arg_error(argv[i], "Option value missing");
                        if (!sscanf(sp, "%qi", (long long *)(void *)&val))
                                arg_error(argv[i], "Integer expected");

                        if (!argv[i][2])
                                argv[i + 1] = NULL;

                        argv[i] = NULL;
                        found = TRUE;
                }
        }

        return val;
}