diff --git a/include/shell/shell.h b/include/shell/shell.h index dcc2c41104b..4246be149d6 100644 --- a/include/shell/shell.h +++ b/include/shell/shell.h @@ -17,7 +17,7 @@ #include #if defined CONFIG_SHELL_GETOPT -#include +#include #endif #ifdef __cplusplus @@ -71,7 +71,6 @@ extern "C" { * @{ */ -struct getopt_state; struct shell_static_entry; /** @@ -707,7 +706,7 @@ struct shell_ctx { #if defined CONFIG_SHELL_GETOPT /*!< getopt context for a shell backend. */ - struct getopt_state getopt_state; + struct getopt_state getopt; #endif uint16_t cmd_buff_len; /*!< Command length.*/ @@ -1028,40 +1027,6 @@ void shell_help(const struct shell *shell); /* @brief Command's help has been printed */ #define SHELL_CMD_HELP_PRINTED (1) -#if defined CONFIG_SHELL_GETOPT -/** - * @brief Parses the command-line arguments. - * - * It is based on FreeBSD implementation. - * - * @param[in] shell Pointer to the shell instance. - * @param[in] argc Arguments count. - * @param[in] argv Arguments. - * @param[in] ostr String containing the legitimate option characters. - * - * @return If an option was successfully found, function returns - * the option character. - * @return If options have been detected that is not in @p ostr - * function will return '?'. - * If function encounters an option with a missing - * argument, then the return value depends on the first - * character in optstring: if it is ':', then ':' is - * returned; otherwise '?' is returned. - * @return -1 If all options have been parsed. - */ -int shell_getopt(const struct shell *shell, int argc, char *const argv[], - const char *ostr); - -/** - * @brief Returns shell_getopt state. - * - * @param[in] shell Pointer to the shell instance. - * - * @return Pointer to struct getopt_state. - */ -struct getopt_state *shell_getopt_state_get(const struct shell *shell); -#endif /* CONFIG_SHELL_GETOPT */ - /** @brief Execute command. * * Pass command line to shell to execute. diff --git a/include/shell/shell_getopt.h b/include/shell/shell_getopt.h deleted file mode 100644 index 789aa11c18b..00000000000 --- a/include/shell/shell_getopt.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2021 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#ifndef SHELL_GETOPT_H__ -#define SHELL_GETOPT_H__ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - - -/* Initializing shell getopt module. - * - * @param[in] shell Pointer to the shell instance. - */ -void z_shell_getopt_init(struct getopt_state *state); - -#ifdef __cplusplus -} -#endif - -#endif /* SHELL_GETOPT_H__ */ diff --git a/lib/posix/CMakeLists.txt b/lib/posix/CMakeLists.txt index 169ddb2cf10..73f1613ec47 100644 --- a/lib/posix/CMakeLists.txt +++ b/lib/posix/CMakeLists.txt @@ -23,6 +23,7 @@ zephyr_library_sources_ifdef(CONFIG_PTHREAD_IPC pthread_key.c) zephyr_library_sources_ifdef(CONFIG_POSIX_MQUEUE mqueue.c) zephyr_library_sources_ifdef(CONFIG_POSIX_FS fs.c) zephyr_library_sources_ifdef(CONFIG_EVENTFD eventfd.c) +add_subdirectory_ifdef(CONFIG_GETOPT getopt) if(NOT (CONFIG_BOARD_NATIVE_POSIX OR CONFIG_BOARD_NATIVE_POSIX_64BIT)) zephyr_library_sources(nanosleep.c) diff --git a/lib/posix/Kconfig b/lib/posix/Kconfig index b1a0d52995b..4d09716a6b8 100644 --- a/lib/posix/Kconfig +++ b/lib/posix/Kconfig @@ -1,6 +1,8 @@ # Copyright (c) 2018 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +source "lib/posix/getopt/Kconfig" + config POSIX_MAX_FDS int "Maximum number of open file descriptors" default 16 if POSIX_API diff --git a/lib/util/getopt/CMakeLists.txt b/lib/posix/getopt/CMakeLists.txt similarity index 70% rename from lib/util/getopt/CMakeLists.txt rename to lib/posix/getopt/CMakeLists.txt index 9c400610d8d..642e3b9949b 100644 --- a/lib/util/getopt/CMakeLists.txt +++ b/lib/posix/getopt/CMakeLists.txt @@ -11,4 +11,10 @@ zephyr_include_directories_ifdef( zephyr_sources_ifdef( CONFIG_GETOPT getopt.c + getopt_common.c +) + +zephyr_sources_ifdef( + CONFIG_GETOPT_LONG + getopt_long.c ) diff --git a/lib/posix/getopt/Kconfig b/lib/posix/getopt/Kconfig new file mode 100644 index 00000000000..1b759648782 --- /dev/null +++ b/lib/posix/getopt/Kconfig @@ -0,0 +1,27 @@ +# Copyright (c) 2021 Nordic Semiconductor +# SPDX-License-Identifier: Apache-2.0 + + +menuconfig GETOPT + bool "Geopt library support" + help + This option adds support of getopt. + Different shell backends are use their own instance of getopt to + not interfere with each other. + All not shell threads share one global instance of getopt state, hence + apart from shell this library is not thread safe. User can add support + for other threads by extending function getopt_state_get in + getopt_common.c file. + This option enables the following function: getopt. + +config GETOPT_LONG + bool "Getopt long library support" + depends on GETOPT + help + This option adds support of the getopt long. + Different shell backends are using their own instance of getopt to + not interfere with each other. + All not shell threads share one global instance of getopt state, hence + apart from shell this library is not thread safe. User can add support + for other threads by extending function getopt_state_get in + getopt_common.c file. diff --git a/lib/util/getopt/README b/lib/posix/getopt/README similarity index 100% rename from lib/util/getopt/README rename to lib/posix/getopt/README diff --git a/lib/util/getopt/getopt.c b/lib/posix/getopt/getopt.c similarity index 82% rename from lib/util/getopt/getopt.c rename to lib/posix/getopt/getopt.c index 7ab963a1bfa..cec7c37755d 100644 --- a/lib/util/getopt/getopt.c +++ b/lib/posix/getopt/getopt.c @@ -31,6 +31,7 @@ #include #include "getopt.h" +#include "getopt_common.h" #include LOG_MODULE_REGISTER(getopt); @@ -39,8 +40,12 @@ LOG_MODULE_REGISTER(getopt); #define BADARG ((int)':') #define EMSG "" -void getopt_init(struct getopt_state *state) +void getopt_init(void) { + struct getopt_state *state; + + state = getopt_state_get(); + state->opterr = 1; state->optind = 1; state->optopt = 0; @@ -48,20 +53,30 @@ void getopt_init(struct getopt_state *state) state->optarg = NULL; state->place = ""; /* EMSG */ + +#if CONFIG_GETOPT_LONG + state->nonopt_start = -1; /* first non option argument (for permute) */ + state->nonopt_end = -1; /* first option after non options (for permute) */ +#endif + + opterr = 1; + optind = 1; + optopt = 0; + optreset = 0; + optarg = NULL; } /* * getopt -- * Parse argc/argv argument vector. */ -int -getopt(state, nargc, nargv, ostr) - struct getopt_state *state; - int nargc; - char *const nargv[]; - const char *ostr; +int getopt(int nargc, char *const nargv[], const char *ostr) { - char *oli; /* option letter list index */ + struct getopt_state *state; + char *oli; /* option letter list index */ + + /* get getopt state of the current thread */ + state = getopt_state_get(); if (state->optreset || *state->place == 0) { /* update scanning pointer */ state->optreset = 0; @@ -69,6 +84,7 @@ getopt(state, nargc, nargv, ostr) if (state->optind >= nargc || *state->place++ != '-') { /* Argument is absent or is not an option */ state->place = EMSG; + z_getopt_global_state_update(state); return -1; } state->optopt = *state->place++; @@ -76,6 +92,7 @@ getopt(state, nargc, nargv, ostr) /* "--" => end of options */ ++state->optind; state->place = EMSG; + z_getopt_global_state_update(state); return -1; } if (state->optopt == 0) { @@ -84,6 +101,7 @@ getopt(state, nargc, nargv, ostr) */ state->place = EMSG; if (strchr(ostr, '-') == NULL) { + z_getopt_global_state_update(state); return -1; } state->optopt = '-'; @@ -101,6 +119,7 @@ getopt(state, nargc, nargv, ostr) if (state->opterr && *ostr != ':') { LOG_ERR("illegal option -- %c", state->optopt); } + z_getopt_global_state_update(state); return BADCH; } @@ -123,16 +142,19 @@ getopt(state, nargc, nargv, ostr) /* option-argument absent */ state->place = EMSG; if (*ostr == ':') { + z_getopt_global_state_update(state); return BADARG; } if (state->opterr) { LOG_ERR("option requires an argument -- %c", state->optopt); } + z_getopt_global_state_update(state); return BADCH; } state->place = EMSG; ++state->optind; } + z_getopt_global_state_update(state); return state->optopt; /* return option letter */ } diff --git a/lib/posix/getopt/getopt.h b/lib/posix/getopt/getopt.h new file mode 100644 index 00000000000..565e096586f --- /dev/null +++ b/lib/posix/getopt/getopt.h @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _GETOPT_H__ +#define _GETOPT_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct getopt_state { + int opterr; /* if error message should be printed */ + int optind; /* index into parent argv vector */ + int optopt; /* character checked for validity */ + int optreset; /* reset getopt */ + char *optarg; /* argument associated with option */ + + char *place; /* option letter processing */ + +#if CONFIG_GETOPT_LONG + int nonopt_start; + int nonopt_end; +#endif +}; + +extern int opterr; /* if error message should be printed */ +extern int optind; /* index into parent argv vector */ +extern int optopt; /* character checked for validity */ +extern int optreset; /* reset getopt */ +extern char *optarg; /* argument associated with option */ + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +struct option { + /* name of long option */ + const char *name; + /* + * one of no_argument, required_argument, and optional_argument: + * whether option takes an argument + */ + int has_arg; + /* if not NULL, set *flag to val when option found */ + int *flag; + /* if flag not NULL, value to set *flag to; else return value */ + int val; +}; + +/* Function intializes getopt_state structure for current thread */ +void getopt_init(void); + +/* Function returns getopt_state structure for the current thread. */ +struct getopt_state *getopt_state_get(void); + +/** + * @brief Parses the command-line arguments. + * + * It is based on FreeBSD implementation. + * + * @param[in] argc Arguments count. + * @param[in] argv Arguments. + * @param[in] options String containing the legitimate option characters. + * + * @return If an option was successfully found, function returns + * the option character. + * @return If options have been detected that is not in @p options + * function will return '?'. + * If function encounters an option with a missing + * argument, then the return value depends on the first + * character in optstring: if it is ':', then ':' is + * returned; otherwise '?' is returned. + * @return -1 If all options have been parsed. + */ +int getopt(int nargc, char *const nargv[], const char *ostr); + +/** + * @brief Parses the command-line arguments. + * + * The getopt_long() function works like @ref getopt() except + * it also accepts long options, started with two dashes. + * + * @note This function is based on FreeBSD implementation but it does not + * support environment variable: POSIXLY_CORRECT. + * + * @param[in] argc Arguments count. + * @param[in] argv Arguments. + * @param[in] options String containing the legitimate option characters. + * @param[in] long_options Pointer to the first element of an array of + * @a struct z_option. + * @param[in] long_idx If long_idx is not NULL, it points to a variable + * which is set to the index of the long option relative + * to @p long_options. + * + * @return If an option was successfully found, function returns + * the option character. + */ +int getopt_long(int nargc, char *const *nargv, + const char *options, const struct option *long_options, + int *idx); + +/** + * @brief Parses the command-line arguments. + * + * The getopt_long_only() function works like @ref getopt_long(), + * but '-' as well as "--" can indicate a long option. If an option that starts + * with '-' (not "--") doesn't match a long option, but does match a short + * option, it is parsed as a short option instead. + * + * @note This function is based on FreeBSD implementation but it does not + * support environment variable: POSIXLY_CORRECT. + * + * @param[in] argc Arguments count. + * @param[in] argv Arguments. + * @param[in] options String containing the legitimate option characters. + * @param[in] long_options Pointer to the first element of an array of + * @a struct option. + * @param[in] long_idx If long_idx is not NULL, it points to a variable + * which is set to the index of the long option relative + * to @p long_options. + * + * @return If an option was successfully found, function returns + * the option character. + */ +int getopt_long_only(int nargc, char *const *nargv, + const char *options, const struct option *long_options, + int *idx); + +#ifdef __cplusplus +} +#endif + +#endif /* _GETOPT_H__ */ diff --git a/lib/posix/getopt/getopt_common.c b/lib/posix/getopt/getopt_common.c new file mode 100644 index 00000000000..dd90890beb7 --- /dev/null +++ b/lib/posix/getopt/getopt_common.c @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "getopt.h" + +/* Referring below variables is not thread safe. They reflects getopt state + * only when 1 thread is using getopt. + * When more threads are using getopt please call getopt_state_get to know + * getopt state for the current thread. + */ +int opterr = 1; /* if error message should be printed */ +int optind = 1; /* index into parent argv vector */ +int optopt; /* character checked for validity */ +int optreset; /* reset getopt */ +char *optarg; /* argument associated with option */ + +/* Common state for all threads that did not have own getopt state. */ +static struct getopt_state m_getopt_common_state = { + .opterr = 1, + .optind = 1, + .optopt = 0, + .optreset = 0, + .optarg = NULL, + + .place = "", /* EMSG */ + +#if CONFIG_GETOPT_LONG + .nonopt_start = -1, /* first non option argument (for permute) */ + .nonopt_end = -1, /* first option after non options (for permute) */ +#endif +}; + +/* This function is not thread safe. All threads using getopt are calling + * this function. + */ +void z_getopt_global_state_update(struct getopt_state *state) +{ + opterr = state->opterr; + optind = state->optind; + optopt = state->optopt; + optreset = state->optreset; + optarg = state->optarg; +} + +/* It is internal getopt API function, it shall not be called by the user. */ +struct getopt_state *getopt_state_get(void) +{ +#if CONFIG_SHELL_GETOPT + k_tid_t tid; + + tid = k_current_get(); + STRUCT_SECTION_FOREACH(shell, sh) { + if (tid == sh->ctx->tid) { + return &sh->ctx->getopt; + } + } +#endif + /* If not a shell thread return a common pointer */ + return &m_getopt_common_state; +} diff --git a/lib/posix/getopt/getopt_common.h b/lib/posix/getopt/getopt_common.h new file mode 100644 index 00000000000..b0ec82bf921 --- /dev/null +++ b/lib/posix/getopt/getopt_common.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _GETOPT_COMMON_H__ +#define _GETOPT_COMMON_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* This function is not thread safe. All threads using getopt are calling + * this function. + */ +void z_getopt_global_state_update(struct getopt_state *state); + +#ifdef __cplusplus +} +#endif + +#endif /* _GETOPT_COMMON_H__ */ diff --git a/lib/posix/getopt/getopt_long.c b/lib/posix/getopt/getopt_long.c new file mode 100644 index 00000000000..67a126715bc --- /dev/null +++ b/lib/posix/getopt/getopt_long.c @@ -0,0 +1,610 @@ +/* $OpenBSD: getopt_long.c,v 1.22 2006/10/04 21:29:04 jmc Exp $ */ +/* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */ + +/* + * Copyright (c) 2002 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ +/* + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * 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 +#include "getopt.h" +#include "getopt_common.h" + +#include +LOG_MODULE_DECLARE(getopt); + +#define GNU_COMPATIBLE /* Be more compatible, configure's use us! */ + +#define PRINT_ERROR ((state->opterr) && (*options != ':')) + +#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ +#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ +#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ + +/* return values */ +#define BADCH (int)'?' +#define BADARG ((*options == ':') ? (int)':' : (int)'?') +#define INORDER 1 + +#define EMSG "" + +#ifdef GNU_COMPATIBLE +#define NO_PREFIX (-1) +#define D_PREFIX 0 +#define DD_PREFIX 1 +#define W_PREFIX 2 +#endif + +static int getopt_internal(struct getopt_state *, int, char * const *, + const char *, const struct option *, int *, int); +static int parse_long_options(struct getopt_state *, char * const *, + const char *, const struct option *, int *, int, + int); +static int gcd(int, int); +static void permute_args(int, int, int, char *const *); + +/* Error messages */ +#define RECARGCHAR "option requires an argument -- %c" +#define ILLOPTCHAR "illegal option -- %c" /* From P1003.2 */ +#ifdef GNU_COMPATIBLE +static int dash_prefix = NO_PREFIX; +#define GNUOPTCHAR "invalid option -- %c" + +#define RECARGSTRING "option `%s%s' requires an argument" +#define AMBIG "option `%s%.*s' is ambiguous" +#define NOARG "option `%s%.*s' doesn't allow an argument" +#define ILLOPTSTRING "unrecognized option `%s%s'" +#else +#define RECARGSTRING "option requires an argument -- %s" +#define AMBIG "ambiguous option -- %.*s" +#define NOARG "option doesn't take an argument -- %.*s" +#define ILLOPTSTRING "unknown option -- %s" +#endif + +/* + * Compute the greatest common divisor of a and b. + */ +static int +gcd(int a, int b) +{ + int c; + + c = a % b; + while (c != 0) { + a = b; + b = c; + c = a % b; + } + + return b; +} + +/* + * Exchange the block from nonopt_start to nonopt_end with the block + * from nonopt_end to opt_end (keeping the same order of arguments + * in each block). + */ +static void +permute_args(int panonopt_start, int panonopt_end, int opt_end, + char * const *nargv) +{ + int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; + char *swap; + + /* + * compute lengths of blocks and number and size of cycles + */ + nnonopts = panonopt_end - panonopt_start; + nopts = opt_end - panonopt_end; + ncycle = gcd(nnonopts, nopts); + cyclelen = (opt_end - panonopt_start) / ncycle; + + for (i = 0; i < ncycle; i++) { + cstart = panonopt_end+i; + pos = cstart; + for (j = 0; j < cyclelen; j++) { + if (pos >= panonopt_end) + pos -= nnonopts; + else + pos += nopts; + swap = nargv[pos]; + /* LINTED const cast */ + ((char **) nargv)[pos] = nargv[cstart]; + /* LINTED const cast */ + ((char **)nargv)[cstart] = swap; + } + } +} + +/* + * parse_long_options -- + * Parse long options in argc/argv argument vector. + * Returns -1 if short_too is set and the option does not match long_options. + */ +static int +parse_long_options(struct getopt_state *state, char * const *nargv, + const char *options, const struct option *long_options, + int *idx, int short_too, int flags) +{ + char *current_argv, *has_equal; +#ifdef GNU_COMPATIBLE + char *current_dash; +#endif + size_t current_argv_len; + int i, match, exact_match, second_partial_match; + + current_argv = state->place; +#ifdef GNU_COMPATIBLE + switch (dash_prefix) { + case D_PREFIX: + current_dash = "-"; + break; + case DD_PREFIX: + current_dash = "--"; + break; + case W_PREFIX: + current_dash = "-W "; + break; + default: + current_dash = ""; + break; + } +#endif + match = -1; + exact_match = 0; + second_partial_match = 0; + + state->optind++; + + has_equal = strchr(current_argv, '='); + if (has_equal != NULL) { + /* argument found (--option=arg) */ + current_argv_len = has_equal - current_argv; + has_equal++; + } else + current_argv_len = strlen(current_argv); + + for (i = 0; long_options[i].name; i++) { + /* find matching long option */ + if (strncmp(current_argv, long_options[i].name, + current_argv_len)) + continue; + + if (strlen(long_options[i].name) == current_argv_len) { + /* exact match */ + match = i; + exact_match = 1; + break; + } + /* + * If this is a known short option, don't allow + * a partial match of a single character. + */ + if (short_too && current_argv_len == 1) + continue; + + if (match == -1) /* first partial match */ + match = i; + else if ((flags & FLAG_LONGONLY) || + long_options[i].has_arg != + long_options[match].has_arg || + long_options[i].flag != long_options[match].flag || + long_options[i].val != long_options[match].val) + second_partial_match = 1; + } + if (!exact_match && second_partial_match) { + /* ambiguous abbreviation */ + if (PRINT_ERROR) + LOG_WRN(AMBIG, +#ifdef GNU_COMPATIBLE + current_dash, +#endif + (int)current_argv_len, + current_argv); + state->optopt = 0; + return BADCH; + } + if (match != -1) { /* option found */ + if (long_options[match].has_arg == no_argument + && has_equal) { + if (PRINT_ERROR) + LOG_WRN(NOARG, +#ifdef GNU_COMPATIBLE + current_dash, +#endif + (int)current_argv_len, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + state->optopt = long_options[match].val; + else + state->optopt = 0; +#ifdef GNU_COMPATIBLE + return BADCH; +#else + return BADARG; +#endif + } + if (long_options[match].has_arg == required_argument || + long_options[match].has_arg == optional_argument) { + if (has_equal) + state->optarg = has_equal; + else if (long_options[match].has_arg == + required_argument) { + /* + * optional argument doesn't use next nargv + */ + state->optarg = nargv[state->optind++]; + } + } + if ((long_options[match].has_arg == required_argument) + && (state->optarg == NULL)) { + /* + * Missing argument; leading ':' indicates no error + * should be generated. + */ + if (PRINT_ERROR) + LOG_WRN(RECARGSTRING, +#ifdef GNU_COMPATIBLE + current_dash, +#endif + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + state->optopt = long_options[match].val; + else + state->optopt = 0; + --state->optind; + return BADARG; + } + } else { /* unknown option */ + if (short_too) { + --state->optind; + return -1; + } + if (PRINT_ERROR) + LOG_WRN(ILLOPTSTRING, +#ifdef GNU_COMPATIBLE + current_dash, +#endif + current_argv); + state->optopt = 0; + return BADCH; + } + if (idx) + *idx = match; + if (long_options[match].flag) { + *long_options[match].flag = long_options[match].val; + return 0; + } else + return long_options[match].val; +} + +/* + * getopt_internal -- + * Parse argc/argv argument vector. Called by user level routines. + */ +static int +getopt_internal(struct getopt_state *state, int nargc, char * const *nargv, + const char *options, const struct option *long_options, + int *idx, int flags) +{ + char *oli; /* option letter list index */ + int optchar, short_too; + + if (options == NULL) + return -1; + + /* + * Disable GNU extensions if options string begins with a '+'. + */ +#ifdef GNU_COMPATIBLE + if (*options == '-') + flags |= FLAG_ALLARGS; + else if (*options == '+') + flags &= ~FLAG_PERMUTE; +#else + if (*options == '+') + flags &= ~FLAG_PERMUTE; + else if (*options == '-') + flags |= FLAG_ALLARGS; +#endif + if (*options == '+' || *options == '-') + options++; + + /* + * XXX Some GNU programs (like cvs) set optind to 0 instead of + * XXX using optreset. Work around this braindamage. + */ + if (state->optind == 0) + state->optind = state->optreset = 1; + + state->optarg = NULL; + if (state->optreset) + state->nonopt_start = state->nonopt_end = -1; +start: + if (state->optreset || !*(state->place)) {/* update scanning pointer */ + state->optreset = 0; + if (state->optind >= nargc) { /* end of argument vector */ + state->place = EMSG; + if (state->nonopt_end != -1) { + /* do permutation, if we have to */ + permute_args(state->nonopt_start, + state->nonopt_end, + state->optind, nargv); + state->optind -= state->nonopt_end - + state->nonopt_start; + } else if (state->nonopt_start != -1) { + /* + * If we skipped non-options, set optind + * to the first of them. + */ + state->optind = state->nonopt_start; + } + state->nonopt_start = state->nonopt_end = -1; + return -1; + } + state->place = nargv[state->optind]; + if (*(state->place) != '-' || +#ifdef GNU_COMPATIBLE + state->place[1] == '\0') { +#else + (state->place[1] == '\0' && strchr(options, '-') == NULL)) { +#endif + state->place = EMSG; /* found non-option */ + if (flags & FLAG_ALLARGS) { + /* + * GNU extension: + * return non-option as argument to option 1 + */ + state->optarg = nargv[state->optind++]; + return INORDER; + } + if (!(flags & FLAG_PERMUTE)) { + /* + * If no permutation wanted, stop parsing + * at first non-option. + */ + return -1; + } + /* do permutation */ + if (state->nonopt_start == -1) { + state->nonopt_start = state->optind; + } else if (state->nonopt_end != -1) { + permute_args(state->nonopt_start, + state->nonopt_end, + state->optind, + nargv); + state->nonopt_start = state->optind - + (state->nonopt_end - state->nonopt_start); + state->nonopt_end = -1; + } + state->optind++; + /* process next argument */ + goto start; + } + if (state->nonopt_start != -1 && state->nonopt_end == -1) { + state->nonopt_end = state->optind; + } + + /* + * If we have "-" do nothing, if "--" we are done. + */ + if (state->place[1] != '\0' && *++(state->place) == '-' && + state->place[1] == '\0') { + state->optind++; + state->place = EMSG; + /* + * We found an option (--), so if we skipped + * non-options, we have to permute. + */ + if (state->nonopt_end != -1) { + permute_args(state->nonopt_start, + state->nonopt_end, + state->optind, + nargv); + state->optind -= state->nonopt_end - + state->nonopt_start; + } + state->nonopt_start = state->nonopt_end = -1; + return -1; + } + } + + /* + * Check long options if: + * 1) we were passed some + * 2) the arg is not just "-" + * 3) either the arg starts with -- we are getopt_long_only() + */ + if (long_options != NULL && state->place != nargv[state->optind] && + (*(state->place) == '-' || (flags & FLAG_LONGONLY))) { + short_too = 0; +#ifdef GNU_COMPATIBLE + dash_prefix = D_PREFIX; +#endif + if (*(state->place) == '-') { + state->place++; /* --foo long option */ +#ifdef GNU_COMPATIBLE + dash_prefix = DD_PREFIX; +#endif + } else if (*(state->place) != ':' && strchr(options, + *(state->place)) != NULL) + short_too = 1; /* could be short option too */ + + optchar = parse_long_options(state, nargv, options, + long_options, idx, short_too, + flags); + if (optchar != -1) { + state->place = EMSG; + return optchar; + } + } + optchar = (int)*(state->place)++; + oli = strchr(options, optchar); + if (optchar == (int)':' || + (optchar == (int)'-' && *(state->place) != '\0') || + oli == NULL) { + /* + * If the user specified "-" and '-' isn't listed in + * options, return -1 (non-option) as per POSIX. + * Otherwise, it is an unknown option character (or ':'). + */ + if (optchar == (int)'-' && *(state->place) == '\0') { + return -1; + } + if (!*(state->place)) { + ++state->optind; + } +#ifdef GNU_COMPATIBLE + if (PRINT_ERROR) { + LOG_WRN(GNUOPTCHAR, optchar); + } +#else + if (PRINT_ERROR) { + LOG_WRN(ILLOPTCHAR, optchar); + } +#endif + state->optopt = optchar; + return BADCH; + } + if (long_options != NULL && optchar == 'W' && oli[1] == ';') { + /* -W long-option */ + if (*(state->place)) { /* no space */ + ; /* NOTHING */ + } else if (++(state->optind) >= nargc) { /* no arg */ + state->place = EMSG; + if (PRINT_ERROR) { + LOG_WRN(RECARGCHAR, optchar); + } + state->optopt = optchar; + return BADARG; + } else if ((state->optind) < nargc) { + state->place = nargv[state->optind]; + } +#ifdef GNU_COMPATIBLE + dash_prefix = W_PREFIX; +#endif + optchar = parse_long_options(state, nargv, options, + long_options, idx, 0, flags); + state->place = EMSG; + return optchar; + } + if (*++oli != ':') { /* doesn't take argument */ + if (!*(state->place)) { + ++state->optind; + } + } else { /* takes (optional) argument */ + state->optarg = NULL; + if (*(state->place)) { /* no white space */ + state->optarg = state->place; + } else if (oli[1] != ':') { /* arg not optional */ + if (++state->optind >= nargc) { /* no arg */ + state->place = EMSG; + if (PRINT_ERROR) { + LOG_WRN(RECARGCHAR, optchar); + } + state->optopt = optchar; + return BADARG; + } + state->optarg = nargv[state->optind]; + } + state->place = EMSG; + ++state->optind; + } + /* dump back option letter */ + return optchar; +} + +/* + * getopt_long -- + * Parse argc/argv argument vector. + */ +int +getopt_long(int nargc, char *const *nargv, + const char *options, const struct option *long_options, + int *idx) +{ + struct getopt_state *state; + int ret; + + /* Get state of the current thread */ + state = getopt_state_get(); + + ret = getopt_internal(state, nargc, nargv, options, long_options, idx, + FLAG_PERMUTE); + + z_getopt_global_state_update(state); + + return ret; +} + +/* + * getopt_long_only -- + * Parse argc/argv argument vector. + */ +int +getopt_long_only(int nargc, char *const *nargv, + const char *options, const struct option *long_options, + int *idx) +{ + struct getopt_state *state; + int ret; + + /* Get state of the current thread */ + state = getopt_state_get(); + + ret = getopt_internal(state, nargc, nargv, options, long_options, idx, + FLAG_PERMUTE|FLAG_LONGONLY); + + z_getopt_global_state_update(state); + + return ret; +} diff --git a/lib/util/CMakeLists.txt b/lib/util/CMakeLists.txt index 8ceb6001b9b..f4a2b08db17 100644 --- a/lib/util/CMakeLists.txt +++ b/lib/util/CMakeLists.txt @@ -1,4 +1,3 @@ # SPDX-License-Identifier: Apache-2.0 add_subdirectory_ifdef(CONFIG_FNMATCH fnmatch) -add_subdirectory_ifdef(CONFIG_GETOPT getopt) diff --git a/lib/util/Kconfig b/lib/util/Kconfig index 70bf491cdee..0907cbba4ae 100644 --- a/lib/util/Kconfig +++ b/lib/util/Kconfig @@ -6,6 +6,4 @@ menu "Util libraries" source "lib/util/fnmatch/Kconfig" -source "lib/util/getopt/Kconfig" - endmenu diff --git a/lib/util/getopt/Kconfig b/lib/util/getopt/Kconfig deleted file mode 100644 index 0f60b8a6412..00000000000 --- a/lib/util/getopt/Kconfig +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) 2021 Nordic Semiconductor -# SPDX-License-Identifier: Apache-2.0 - -config GETOPT - bool "GetOpt Support" - help - This option enables the getopt library diff --git a/lib/util/getopt/getopt.h b/lib/util/getopt/getopt.h deleted file mode 100644 index c1fb22ab3bd..00000000000 --- a/lib/util/getopt/getopt.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2021 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#ifndef _GETOPT_H__ -#define _GETOPT_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -struct getopt_state { - int opterr; /* if error message should be printed */ - int optind; /* index into parent argv vector */ - int optopt; /* character checked for validity */ - int optreset; /* reset getopt */ - char *optarg; /* argument associated with option */ - - char *place; /* option letter processing */ -}; - -/* Function intializes getopt_state structure */ -void getopt_init(struct getopt_state *state); - -/* - * getopt -- - * Parse argc/argv argument vector. - */ -int getopt(struct getopt_state *const state, int nargc, - char *const nargv[], const char *ostr); - - -#ifdef __cplusplus -} -#endif - -#endif /* _GETOPT_H__ */ diff --git a/samples/subsys/shell/shell_module/sample.yaml b/samples/subsys/shell/shell_module/sample.yaml index 4861364bda5..2fb6dcdb7cb 100644 --- a/samples/subsys/shell/shell_module/sample.yaml +++ b/samples/subsys/shell/shell_module/sample.yaml @@ -22,7 +22,10 @@ tests: harness: keyboard extra_args: CONF_FILE="prj_minimal.conf" sample.shell.shell_module.getopt: - filter: CONFIG_SERIAL and dt_chosen_enabled("zephyr,shell-uart") + integration_platforms: + - qemu_x86 + platform_exclude: native_posix native_posix_64 + filter: CONFIG_SERIAL and dt_chosen_enabled("zephyr,shell-uart") and not CONFIG_NEWLIB_LIBC tags: shell harness: keyboard min_ram: 40 diff --git a/samples/subsys/shell/shell_module/src/main.c b/samples/subsys/shell/shell_module/src/main.c index 9306e45c8ea..13bc7bb8c61 100644 --- a/samples/subsys/shell/shell_module/src/main.c +++ b/samples/subsys/shell/shell_module/src/main.c @@ -100,7 +100,9 @@ static int cmd_demo_board(const struct shell *sh, size_t argc, char **argv) } #if defined CONFIG_SHELL_GETOPT -static int cmd_demo_getopt(const struct shell *shell, size_t argc, char **argv) +/* Thread save usage */ +static int cmd_demo_getopt_ts(const struct shell *sh, size_t argc, + char **argv) { struct getopt_state *state; char *cvalue = NULL; @@ -108,8 +110,8 @@ static int cmd_demo_getopt(const struct shell *shell, size_t argc, char **argv) int bflag = 0; int c; - while ((c = shell_getopt(shell, argc, argv, "abhc:")) != -1) { - state = shell_getopt_state_get(shell); + while ((c = getopt(argc, argv, "abhc:")) != -1) { + state = getopt_state_get(); switch (c) { case 'a': aflag = 1; @@ -125,19 +127,19 @@ static int cmd_demo_getopt(const struct shell *shell, size_t argc, char **argv) * command handler to print help message. It must * be done explicitly. */ - shell_help(shell); + shell_help(sh); return SHELL_CMD_HELP_PRINTED; case '?': if (state->optopt == 'c') { - shell_print(shell, + shell_print(sh, "Option -%c requires an argument.", state->optopt); } else if (isprint(state->optopt)) { - shell_print(shell, + shell_print(sh, "Unknown option `-%c'.", state->optopt); } else { - shell_print(shell, + shell_print(sh, "Unknown option character `\\x%x'.", state->optopt); } @@ -147,7 +149,56 @@ static int cmd_demo_getopt(const struct shell *shell, size_t argc, char **argv) } } - shell_print(shell, "aflag = %d, bflag = %d", aflag, bflag); + shell_print(sh, "aflag = %d, bflag = %d", aflag, bflag); + return 0; +} + +static int cmd_demo_getopt(const struct shell *sh, size_t argc, + char **argv) +{ + char *cvalue = NULL; + int aflag = 0; + int bflag = 0; + int c; + + while ((c = getopt(argc, argv, "abhc:")) != -1) { + switch (c) { + case 'a': + aflag = 1; + break; + case 'b': + bflag = 1; + break; + case 'c': + cvalue = optarg; + break; + case 'h': + /* When getopt is active shell is not parsing + * command handler to print help message. It must + * be done explicitly. + */ + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + case '?': + if (optopt == 'c') { + shell_print(sh, + "Option -%c requires an argument.", + optopt); + } else if (isprint(optopt)) { + shell_print(sh, "Unknown option `-%c'.", + optopt); + } else { + shell_print(sh, + "Unknown option character `\\x%x'.", + optopt); + } + return 1; + default: + break; + } + } + + shell_print(sh, "aflag = %d, bflag = %d", aflag, bflag); return 0; } #endif @@ -322,8 +373,12 @@ SHELL_STATIC_SUBCMD_SET_CREATE(sub_demo, SHELL_CMD(ping, NULL, "Ping command.", cmd_demo_ping), SHELL_CMD(board, NULL, "Show board name command.", cmd_demo_board), #if defined CONFIG_SHELL_GETOPT - SHELL_CMD(getopt, NULL, "Cammand using getopt, looking for: \"abhc:\".", - cmd_demo_getopt), + SHELL_CMD(getopt_thread_safe, NULL, + "Cammand using getopt in thread safe way" + " looking for: \"abhc:\".", + cmd_demo_getopt_ts), + SHELL_CMD(getopt, NULL, "Cammand using getopt in non thread safe way" + " looking for: \"abhc:\".\n", cmd_demo_getopt), #endif SHELL_SUBCMD_SET_END /* Array terminated. */ ); diff --git a/subsys/shell/CMakeLists.txt b/subsys/shell/CMakeLists.txt index cd6e4c86e82..ac8c3b2186f 100644 --- a/subsys/shell/CMakeLists.txt +++ b/subsys/shell/CMakeLists.txt @@ -21,11 +21,6 @@ zephyr_sources_ifdef( shell_help.c ) -zephyr_sources_ifdef( - CONFIG_SHELL_GETOPT - shell_getopt.c - ) - zephyr_sources_ifdef( CONFIG_SHELL_CMDS shell_cmds.c diff --git a/subsys/shell/Kconfig b/subsys/shell/Kconfig index 2234192914f..9b631a63471 100644 --- a/subsys/shell/Kconfig +++ b/subsys/shell/Kconfig @@ -138,10 +138,13 @@ config SHELL_VT100_COLORS If enabled VT100 colors are used in shell (e.g. print errors in red). config SHELL_GETOPT - bool "Enable getopt support" + bool "Enable threadsafe getopt support in shell" select GETOPT help - Enables getopt support in the shell. + This config creates a separate getopt_state for the shell instance. + It ensures that using getopt with shell is thread safe. + When more threads are using getopt please call getopt_state_get to + get getopt state of the shell thread. config SHELL_METAKEYS bool "Enable metakeys" diff --git a/subsys/shell/shell.c b/subsys/shell/shell.c index 7ace47de310..0a03672161c 100644 --- a/subsys/shell/shell.c +++ b/subsys/shell/shell.c @@ -547,7 +547,7 @@ static int exec_cmd(const struct shell *shell, size_t argc, const char **argv, if (!ret_val) { #if CONFIG_SHELL_GETOPT - z_shell_getopt_init(&shell->ctx->getopt_state); + getopt_init(); #endif z_flag_cmd_ctx_set(shell, true); diff --git a/subsys/shell/shell_getopt.c b/subsys/shell/shell_getopt.c deleted file mode 100644 index 9b1dea14463..00000000000 --- a/subsys/shell/shell_getopt.c +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2021 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include - -void z_shell_getopt_init(struct getopt_state *state) -{ - getopt_init(state); -} - -int shell_getopt(const struct shell *shell, int argc, char *const argv[], - const char *ostr) -{ - if (!IS_ENABLED(CONFIG_SHELL_GETOPT)) { - return 0; - } - - __ASSERT_NO_MSG(shell); - - return getopt(&shell->ctx->getopt_state, argc, argv, ostr); -} - -struct getopt_state *shell_getopt_state_get(const struct shell *shell) -{ - if (!IS_ENABLED(CONFIG_SHELL_GETOPT)) { - return NULL; - } - - __ASSERT_NO_MSG(shell); - - return &shell->ctx->getopt_state; -}