diff --git a/doc/reference/shell/index.rst b/doc/reference/shell/index.rst index 15e46183ff72e7..2ce8c247e0d88e 100644 --- a/doc/reference/shell/index.rst +++ b/doc/reference/shell/index.rst @@ -30,7 +30,7 @@ interaction is required. This module is a Unix-like shell with these features: * Built-in handler to display help for the commands. * Support for wildcards: ``*`` and ``?``. * Support for meta keys. -* Support for getopt. +* Support for getopt and getopt_long. * Kconfig configuration to optimize memory usage. .. note:: @@ -486,9 +486,9 @@ Getopt Feature Some shell users apart from subcommands might need to use options as well. the arguments string, looking for supported options. Typically, this task -is accomplished by the ``getopt`` function. +is accomplished by the ``getopt`` familly functions. -For this purpose shell supports the getopt library available +For this purpose shell supports the getopt and getopt_long libraries available in the FreeBSD project. I was modified so that it can be used by all instances of the shell at the same time, hence its call requires one more parameter. diff --git a/include/shell/shell.h b/include/shell/shell.h index c48907baa4acaf..59d56220fc284c 100644 --- a/include/shell/shell.h +++ b/include/shell/shell.h @@ -71,7 +71,7 @@ extern "C" { * @{ */ -struct getopt_state; +struct z_option; struct shell_static_entry; /** @@ -665,7 +665,7 @@ struct shell_ctx { #if defined CONFIG_SHELL_GETOPT /*!< getopt context for a shell backend. */ - struct getopt_state getopt_state; + struct getopt_s getopt_vars; #endif uint16_t cmd_buff_len; /*!< Command length.*/ @@ -978,7 +978,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. * @@ -987,11 +986,11 @@ void shell_help(const struct shell *shell); * @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. + * @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 ostr + * @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 @@ -1000,17 +999,69 @@ void shell_help(const struct shell *shell); * @return -1 If all options have been parsed. */ int shell_getopt(const struct shell *shell, int argc, char *const argv[], - const char *ostr); + const char *options); +/** + * @brief Parses the command-line arguments. + * + * The shell_getopt_long() function works like @ref shell_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] shell Pointer to the shell instance. + * @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 shell_getopt_long(const struct shell *shell, int argc, char *const argv[], + const char *options, const struct z_option *long_options, + int *long_idx); + +/** + * @brief Parses the command-line arguments. + * + * The shell_getopt_long_only() function works like @ref shell_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] shell Pointer to the shell instance. + * @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 shell_getopt_long_only(const struct shell *shell, int argc, + char *const argv[], const char *options, + const struct z_option *long_options, int *long_idx); /** * @brief Returns shell_getopt state. * * @param[in] shell Pointer to the shell instance. * - * @return Pointer to struct getopt_state. + * @return Pointer to @p struct getopt_s. */ -struct getopt_state *shell_getopt_state_get(const struct shell *shell); -#endif /* CONFIG_SHELL_GETOPT */ +struct getopt_s *shell_getopt_state_get(const struct shell *shell); /** @brief Execute command. * diff --git a/include/shell/shell_getopt.h b/include/shell/shell_getopt.h index 789aa11c18bfac..6cbe8d5e31cd50 100644 --- a/include/shell/shell_getopt.h +++ b/include/shell/shell_getopt.h @@ -18,7 +18,7 @@ extern "C" { * * @param[in] shell Pointer to the shell instance. */ -void z_shell_getopt_init(struct getopt_state *state); +void z_shell_getopt_init(struct getopt_s *state); #ifdef __cplusplus } diff --git a/lib/util/getopt/CMakeLists.txt b/lib/util/getopt/CMakeLists.txt index 9c400610d8da58..6f76eb061562a6 100644 --- a/lib/util/getopt/CMakeLists.txt +++ b/lib/util/getopt/CMakeLists.txt @@ -11,4 +11,7 @@ zephyr_include_directories_ifdef( zephyr_sources_ifdef( CONFIG_GETOPT getopt.c + getopt_long.c + lib_getopt_common.c + lib_getoptvars.c ) diff --git a/lib/util/getopt/Kconfig b/lib/util/getopt/Kconfig index 0f60b8a64129a9..d514fcab9a36a4 100644 --- a/lib/util/getopt/Kconfig +++ b/lib/util/getopt/Kconfig @@ -1,7 +1,20 @@ # Copyright (c) 2021 Nordic Semiconductor # SPDX-License-Identifier: Apache-2.0 + config GETOPT - bool "GetOpt Support" + bool "Geopt library support" help - This option enables the getopt library + This option adds support of the multi-instance getopt. + Different shell backends use their own instance of getopt state + structure. + There are two options if one wants to use getopt functions from + outside the shell thread. If getopt is called from only one thread + it can be done straight forward. The getopt functions will use + a single getopt state structure created in the lib_getoptvars.c + file. + However, if getopt is expected to be called from multiple threads, + you must modify the struct getopt_s *getoptvars(void) function + in the lib_getoptvars.c file so that it returns a separate getopt + state structure for each thread. + diff --git a/lib/util/getopt/README b/lib/util/getopt/README deleted file mode 100644 index e0742b9554e503..00000000000000 --- a/lib/util/getopt/README +++ /dev/null @@ -1,58 +0,0 @@ -[GetOpt] -##################### - -Origin: - [Lattera FreeBSD] - [https://github.com/lattera/freebsd/blob/master/lib/libc/stdlib/getopt.c] - -Status: - [So far Zephyr samples were using getopt implementation from: argtable3.c.] - -Purpose: - [Shell users would like to parse options passed to the command handler.] - -Description: - [This library is going to be used by the shell module. Some shell users - are not satisfied with subcommands alone and need to use the options for - commands as well. A library is needed that allows the developer to parse - the arguments string, looking for supported options. Typically, this task - is accomplished by the getopt function. - - For this purpose I decided to port the getopt library available - in the FreeBSD project. I had to modify it so that it could be used - by all instances of the shell at the same time. - - Originally this function was using global variables which were defining - its state. In my implementation I put those variables in a structure - and a pointer to that structure is passed as an additional parameter - to getopt function. In proposed implementation each shell backend has its - own getopt function state structure which it uses. - - This module is intended to be used inside the shell command handler - by the abstraction layer "SHELL_GETOPT". For example: - while ((char c = shell_getopt(shell, argc, argv, "abhc:")) != -1) { - /* some code */ - } - ] - -Dependencies: - [This package does not depend on any other component. - - Zephyr project will only need this component if the user configures a shell - module: SHELL_GETOPT.] - -URL: - [https://github.com/lattera/freebsd] - -commit: - [324e4c082c14aebf00f8dbee0fb7ad285393756a] - -Maintained-by: - [External] - -License: - [BSD 3-Clause "New" or "Revised" License] - -License Link: - [BSD 3-Clause used in getopt: https://spdx.org/licenses/BSD-3-Clause.html] - [Project license: https://github.com/lattera/freebsd/blob/master/COPYRIGHT] diff --git a/lib/util/getopt/getopt.c b/lib/util/getopt/getopt.c index 7ab963a1bfa2bf..a231dae260af07 100644 --- a/lib/util/getopt/getopt.c +++ b/lib/util/getopt/getopt.c @@ -1,138 +1,86 @@ -/* $NetBSD: getopt.c,v 1.26 2003/08/07 16:43:40 agc Exp $ */ -/* SPDX-License-Identifier: BSD-3-Clause */ -/* - * Copyright (c) 1987, 1993, 1994 - * The Regents of the University of California. All rights reserved. +/**************************************************************************** * - * 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. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. + * SPDX-License-Identifier: Apache-2.0 * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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 -LOG_MODULE_REGISTER(getopt); - -#define BADCH ((int)'?') -#define BADARG ((int)':') -#define EMSG "" - -void getopt_init(struct getopt_state *state) -{ - state->opterr = 1; - state->optind = 1; - state->optopt = 0; - state->optreset = 0; - state->optarg = NULL; + * libs/libc/unistd/lib_getopt.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ - state->place = ""; /* EMSG */ -} +#include "getopt_unistd.h" /* - * getopt -- - * Parse argc/argv argument vector. + * Name: getopt + * + * Description: + * getopt() parses command-line arguments. Its arguments argc and argv + * are the argument count and array as passed to the main() function on + * program invocation. An element of argv that starts with '-' is an + * option element. The characters of this element (aside from the initial + * '-') are option characters. If getopt() is called repeatedly, it + * returns successively each of the option characters from each of the + * option elements. + * + * If getopt() finds another option character, it returns that character, + * updating the external variable optind and a static variable nextchar so + * that the next call to getopt() can resume the scan with the following + * option character or argv-element. + * + * If there are no more option characters, getopt() returns -1. Then optind + * is the index in argv of the first argv-element that is not an option. + * + * The 'optstring' argument is a string containing the legitimate option + * characters. If such a character is followed by a colon, this indicates + * that the option requires an argument. If an argument is required for an + * option so getopt() places a pointer to the following text in the same + * argv-element, or the text of the following argv-element, in optarg. + * + * NOTES: + * 1. opterr is not supported and this implementation of getopt() never + * printfs error messages. + * 2. getopt is NOT threadsafe! + * 3. This version of getopt() does not reset global variables until + * -1 is returned. As a result, your command line parsing loops + * must call getopt() repeatedly and continue to parse if other + * errors are returned ('?' or ':') until getopt() finally returns -1. + * (You can also set optind to -1 to force a reset). + * 4. Standard getopt() permutes the contents of argv as it scans, so that + * eventually all the nonoptions are at the end. This implementation + * does not do this. + * + * Returned Value: + * If an option was successfully found, then getopt() returns the option + * character. If all command-line options have been parsed, then getopt() + * returns -1. If getopt() encounters an option character that was not + * in optstring, then '?' is returned. If getopt() 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. + * */ -int -getopt(state, nargc, nargv, ostr) - struct getopt_state *state; - int nargc; - char *const nargv[]; - const char *ostr; + +int getopt(int argc, char * const argv[], const char *optstring) { - char *oli; /* option letter list index */ + int ret; - if (state->optreset || *state->place == 0) { /* update scanning pointer */ - state->optreset = 0; - state->place = nargv[state->optind]; - if (state->optind >= nargc || *state->place++ != '-') { - /* Argument is absent or is not an option */ - state->place = EMSG; - return -1; - } - state->optopt = *state->place++; - if (state->optopt == '-' && *state->place == 0) { - /* "--" => end of options */ - ++state->optind; - state->place = EMSG; - return -1; - } - if (state->optopt == 0) { - /* Solitary '-', treat as a '-' option - * if the program (eg su) is looking for it. - */ - state->place = EMSG; - if (strchr(ostr, '-') == NULL) { - return -1; - } - state->optopt = '-'; - } - } else { - state->optopt = *state->place++; - } + ret = getopt_common(argc, argv, optstring, NULL, NULL, GETOPT_MODE); - /* See if option letter is one the caller wanted... */ - oli = strchr(ostr, state->optopt); - if (state->optopt == ':' || oli == NULL) { - if (*state->place == 0) { - ++state->optind; - } - if (state->opterr && *ostr != ':') { - LOG_ERR("illegal option -- %c", state->optopt); - } - return BADCH; - } + /* set global getopt variables - this is not thread safe */ + global_getopt_vars_set(getoptvars()); - /* Does this option need an argument? */ - if (oli[1] != ':') { - /* don't need argument */ - state->optarg = NULL; - if (*state->place == 0) { - ++state->optind; - } - } else { - /* Option-argument is either the rest of this argument or the - * entire next argument. - */ - if (*state->place) { - state->optarg = state->place; - } else if (nargc > ++state->optind) { - state->optarg = nargv[state->optind]; - } else { - /* option-argument absent */ - state->place = EMSG; - if (*ostr == ':') { - return BADARG; - } - if (state->opterr) { - LOG_ERR("option requires an argument -- %c", - state->optopt); - } - return BADCH; - } - state->place = EMSG; - ++state->optind; - } - return state->optopt; /* return option letter */ + return ret; } diff --git a/lib/util/getopt/getopt.h b/lib/util/getopt/getopt.h index c1fb22ab3bdbb6..edf691cfb29a05 100644 --- a/lib/util/getopt/getopt.h +++ b/lib/util/getopt/getopt.h @@ -1,41 +1,171 @@ -/* - * Copyright (c) 2021 Nordic Semiconductor ASA +/**************************************************************************** * * SPDX-License-Identifier: Apache-2.0 - */ + * + * include/nuttx/lib/getopt.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ -#ifndef _GETOPT_H__ -#define _GETOPT_H__ +#ifndef __INCLUDE_NUTTX_LIB_GETOPT_H +#define __INCLUDE_NUTTX_LIB_GETOPT_H + +#include +#include "getopt_unistd.h" #ifdef __cplusplus extern "C" { #endif -#include +/* This structure encapsulates all variables associated with getopt(). + * These variables are thread specific (shell instance specific) + */ +struct getopt_s { + /* Part of the implementation of the public getopt() interface */ -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 *go_optarg; /* Optional argument following option */ + int go_opterr; /* Print error message */ + int go_optind; /* Index into argv */ + int go_optopt; /* unrecognized option character */ - char *place; /* option letter processing */ + /* Internal getopt() state */ + char *go_optptr; /* Current parsing location */ + bool go_binitialized; /* true: getopt() has been initialized */ }; -/* Function intializes getopt_state structure */ -void getopt_init(struct getopt_state *state); +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +struct option { + const char *name; + int has_arg; + int *flag; + int val; +}; + +/* Refering below values is safe only if only one thread / shell instance is + * using getopt, otherwise each getopt call by the different thread will be + * overwriting them. + * Each shell instance has its own getopt_s structure so the function will + * always work correctly. However, below global variables may not be correct. + * If there are multiple instances of the shell and each uses getopt then + * the current state of the function can be obtained by calling + * getopt_state_get(); + */ +extern char *optarg; +extern int opterr; +extern int optind; +extern int optopt; /* - * getopt -- - * Parse argc/argv argument vector. + * Name: getopt_state_get(void) + * + * Description: + * + * Function returns getopt state for currently executed thread. + * If only one thread is accessing getopt functions user can safely + * use global variables: optarg, opterr, optind, optopt. */ -int getopt(struct getopt_state *const state, int nargc, - char *const nargv[], const char *ostr); +struct getopt_s *getopt_state_get(void); +/* + * Name: getopt + * + * Description: + * getopt() parses command-line arguments. Its arguments argc and argv + * are the argument count and array as passed to the main() function on + * program invocation. An element of argv that starts with '-' is an + * option element. The characters of this element (aside from the initial + * '-') are option characters. If getopt() is called repeatedly, it + * returns successively each of the option characters from each of the + * option elements. + * + * If getopt() finds another option character, it returns that character, + * updating the external variable optind and a static variable nextchar so + * that the next call to getopt() can resume the scan with the following + * option character or argv-element. + * + * If there are no more option characters, getopt() returns -1. Then optind + * is the index in argv of the first argv-element that is not an option. + * + * The 'optstring' argument is a string containing the legitimate option + * characters. If such a character is followed by a colon, this indicates + * that the option requires an argument. If an argument is required for an + * option so getopt() places a pointer to the following text in the same + * argv-element, or the text of the following argv-element, in optarg. + * + * NOTES: + * 1. opterr is not supported and this implementation of getopt() never + * printfs error messages. + * 2. getopt is NOT threadsafe! + * 3. This version of getopt() does not reset global variables until + * -1 is returned. As a result, your command line parsing loops + * must call getopt() repeatedly and continue to parse if other + * errors are returned ('?' or ':') until getopt() finally returns -1. + * (You can also set optind to -1 to force a reset). + * 4. Standard getopt() permutes the contents of argv as it scans, so that + * eventually all the nonoptions are at the end. This implementation + * does not do this. + * + * Returned Value: + * If an option was successfully found, then getopt() returns the option + * character. If all command-line options have been parsed, then getopt() + * returns -1. If getopt() encounters an option character that was not + * in optstring, then '?' is returned. If getopt() 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. + * + */ +int getopt(int argc, char * const argv[], const char *optstring); + +/* + * Name: getopt_long + * + * Description: + * The getopt_long() function works like getopt() except that it also + * accepts long options, started with two dashes. (If the program accepts + * only long options, then optstring should be specified as an empty + * string (""), not NULL.) Long option names may be abbreviated if the + * abbreviation is unique or is an exact match for some defined option + * + */ +int getopt_long(int argc, char * const argv[], + const char *optstring, + const struct option *longopts, + int *longindex); + +/* Name: getopt_long_only + * + * Description: + * getopt_long_only() is like 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. + * + */ +int getopt_long_only(int argc, char * const argv[], + const char *optstring, + const struct option *longopts, + int *longindex); #ifdef __cplusplus } #endif -#endif /* _GETOPT_H__ */ +#endif /* __INCLUDE_NUTTX_LIB_GETOPT_H */ diff --git a/lib/util/getopt/getopt_long.c b/lib/util/getopt/getopt_long.c new file mode 100644 index 00000000000000..a10d07d17f607c --- /dev/null +++ b/lib/util/getopt/getopt_long.c @@ -0,0 +1,69 @@ +/**************************************************************************** + * + * SPDX-License-Identifier: Apache-2.0 + * + * libs/libc/unistd/lib_getopt_long.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#include "getopt_unistd.h" + +/* + * Name: getopt_long + * + * Description: + * The getopt_long() function works like getopt() except that it also + * accepts long options, started with two dashes. (If the program accepts + * only long options, then optstring should be specified as an empty + * string (""), not NULL.) Long option names may be abbreviated if the + * abbreviation is unique or is an exact match for some defined option + * + */ + +int getopt_long(int argc, char * const argv[], + const char *optstring, + const struct option *longopts, + int *longindex) +{ + int ret; + ret = getopt_common(argc, argv, optstring, longopts, longindex, + GETOPT_LONG_MODE); + + /* set global getopt variables - this is not thread safe */ + global_getopt_vars_set(getoptvars()); + + return ret; +} + +int getopt_long_only(int argc, char * const argv[], + const char *optstring, + const struct option *longopts, + int *longindex) +{ + int ret; + + ret = getopt_common(argc, argv, optstring, longopts, longindex, + GETOPT_LONG_ONLY_MODE); + + /* set global getopt variables - this is not thread safe */ + global_getopt_vars_set(getoptvars()); + + return ret; +} + + diff --git a/lib/util/getopt/getopt_unistd.h b/lib/util/getopt/getopt_unistd.h new file mode 100644 index 00000000000000..592657c61807e6 --- /dev/null +++ b/lib/util/getopt/getopt_unistd.h @@ -0,0 +1,102 @@ +/**************************************************************************** + * + * SPDX-License-Identifier: Apache-2.0 + * + * libs/libc/unistd/unistd.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __LIBC_UNISTD_UNISTD_H +#define __LIBC_UNISTD_UNISTD_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include "getopt.h" + +struct option; + +#ifdef __cplusplus +extern "C" { +#endif + +/* Mode bit definitions */ + +#define GETOPT_LONG_BIT (1 << 0) /* Long options supported */ +#define GETOPT_LONGONLY_BIT (1 << 1) /* Long-Only behavior supported */ + +#define GETOPT_HAVE_LONG(m) (((m) & GETOPT_LONG_BIT) != 0) +#define GETOPT_HAVE_LONGONLY(m) (((m) & GETOPT_LONGONLY_BIT) != 0) + + +/* The mode determines which of getopt(), getopt_long(), and + * getopt_long_only() that is being emulated by getopt_common(). + */ + +enum getopt_mode_e { + GETOPT_MODE = 0, + GETOPT_LONG_MODE = GETOPT_LONG_BIT, + GETOPT_LONG_ONLY_MODE = (GETOPT_LONG_BIT | GETOPT_LONGONLY_BIT) +}; + +/* + * Name: getoptvars + * + * Description: + * Returns a pointer to to the thread-specific getopt() data. + * + */ + +struct getopt_s *getoptvars(void); + + +/* + * Name: getoptvars_init + * + * Description: + * Initializes getopt_s structure with default values. + * + */ + +void getoptvars_init(struct getopt_s *getopt_vars); + +/* + * Name: getopt_common + * + * Description: + * getopt_common() is the common, internal implementation of getopt(), + * getopt_long(), and getopt_long_only(). + * + */ + +int getopt_common(int argc, char * const argv[], + const char *optstring, + const struct option *longopts, + int *longindex, + enum getopt_mode_e mode); + +void global_getopt_vars_set(struct getopt_s *go); + +#ifdef __cplusplus +} +#endif + +#endif /* __LIBC_UNISTD_UNISTD_H */ diff --git a/lib/util/getopt/lib_getopt_common.c b/lib/util/getopt/lib_getopt_common.c new file mode 100644 index 00000000000000..c09a07ac82e51f --- /dev/null +++ b/lib/util/getopt/lib_getopt_common.c @@ -0,0 +1,545 @@ +/**************************************************************************** + * libs/libc/unistd/lib_getopt_common.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#include +#include + +#include "getopt_unistd.h" + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 +#define ERROR -1 +#define OK 0 + +char *optarg = NULL; +int opterr = 0; +int optind = 1; +int optopt = '?'; + +/* + * Name: compare_long_option + * + * Description: + * Compare a command argument with a long option, handling the cases: + * + * --option: Any argument is in the next argv entry + * --option=argument: The argument is in the same argv entry + * + */ + +static int compare_long_option(const char *cmdarg, + const char *optname, + const char **argument) +{ + int result; + + *argument = NULL; + + for (; ; ) { + int rawchar = *cmdarg++; + int optchar = *optname++; + int cmdchar; + + /* The command line option may terminate with either '\0' or '='. */ + + cmdchar = rawchar; + if (cmdchar == '=') { + cmdchar = '\0'; + } + + /* Perform the comparison */ + result = cmdchar - optchar; + + if (result != 0 || cmdchar == '\0') { + /* If the '=' is the real terminator, then + * return the argument that follows the '=' + */ + if (rawchar == '=') { + *argument = cmdarg; + } + break; + } + } + return result; +} + +/* + * Name: getopt_long_option + * + * Description: + * Handle one long option + * + */ +static int getopt_long_option(struct getopt_s *go, + char * const argv[], + const struct option *longopts, + int *longindex) +{ + int ndx; + int ret; + + /* The option list may not be NULL in this context */ + + if (longopts == NULL) { + goto errout; + } + + /* Search the list of long options for a matching name. + * The last element of the option arry must be filled with zeroes. + */ + + for (ndx = 0; longopts[ndx].name != NULL; ndx++) { + char *terminator = NULL; + + if (compare_long_option(go->go_optptr, longopts[ndx].name, + (const char **)&terminator) == 0) { + /* Found the option with the matching name. Does it have an + * argument provided in the same argv entry like + * --option=argument? + */ + + if (terminator != NULL) { + /* Skip over the option + argument */ + + go->go_optptr = NULL; + go->go_optind++; + + switch (longopts[ndx].has_arg) { + case no_argument: + /* But no argument is expected! */ + go->go_optarg = NULL; + return '?'; + case optional_argument: + case required_argument: + + /* Return the required argument */ + go->go_optarg = terminator; + break; + + default: + goto errout; + break; + } + } else { + /* Does the option have a required argument in the + * next argv entry? An optional argument? + */ + switch (longopts[ndx].has_arg) { + char *next; + case no_argument: + /* No, no arguments. Just return the argument + * that we found. + */ + go->go_optptr = NULL; + go->go_optind++; + break; + case optional_argument: + /* Check if there is a following argument and + * if that following argument is another option. + */ + next = argv[go->go_optind + 1]; + if (next == NULL || next[0] == '-') { + go->go_optptr = NULL; + go->go_optarg = NULL; + go->go_optind++; + break; + } + /* Fall through and treat as a required option */ + case required_argument: + /* Verify that the required argument is present */ + next = argv[go->go_optind + 1]; + if (next == NULL || next[0] == '-') { + go->go_optptr = NULL; + go->go_optarg = NULL; + go->go_optind++; + return '?'; + } + /* Return the required argument */ + go->go_optptr = NULL; + go->go_optarg = next; + go->go_optind += 2; + break; + default: + goto errout; + break; + } + } + + /* Setup return value. + * + * 'val' is the value to return on success, or to load into the + * variable pointed to by flag. + * + * 'flag' specifies how results are returned for a long option. If + * flag is NULL, then getopt_long() returns val. Otherwise, + * getopt_long() returns 0, and flag points to a variable which is + * set to val if the option is found, but left unchanged if the + * option is not found. + */ + + if (longopts[ndx].flag != NULL) { + *(longopts[ndx].flag) = longopts[ndx].val; + ret = OK; + } + else { + ret = longopts[ndx].val; + } + + /* If longindex is not NULL, it points to a variable which is + * set to the index of the long option relative to longopts. + */ + + if (longindex != NULL) { + *longindex = ndx; + } + + return ret; + } + } + /* This option is not in the list of valid options */ + + go->go_optopt = *go->go_optptr; + return '?'; + +errout: + + /* Restore the initial, uninitialized state */ + + go->go_binitialized = false; + return ERROR; +} + +/* + * Name: getopt_common + * + * Description: + * + * getopt_common() is the common, internal implementation of getopt(), + * getopt_long(), and getopt_long_only(). + * + * getopt() parses command-line arguments. Its arguments argc and argv + * are the argument count and array as passed to the main() function on + * program invocation. An element of argv that starts with '-' is an + * option element. The characters of this element (aside from the initial + * '-') are option characters. If getopt() is called repeatedly, it + * returns successively each of the option characters from each of the + * option elements. + * + * If getopt() finds another option character, it returns that character, + * updating the external variable optind and a static variable nextchar so + * that the next call to getopt() can resume the scan with the following + * option character or argv-element. + * + * If there are no more option characters, getopt() returns -1. Then optind + * is the index in argv of the first argv-element that is not an option. + * + * The 'optstring' argument is a string containing the legitimate option + * characters. If such a character is followed by a colon, this indicates + * that the option requires an argument. If an argument is required for an + * option so getopt() places a pointer to the following text in the same + * argv-element, or the text of the following argv-element, in optarg. + * + * The getopt_long() function works like getopt() except that it also + * accepts long options, started with two dashes. (If the program accepts + * only long options, then optstring should be specified as an empty + * string (""), not NULL.) Long option names may be abbreviated if the + * abbreviation is unique or is an exact match for some defined option + * + * getopt_long_only() is like 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. + * + * NOTES: + * 1. opterr is not supported and this implementation of getopt() never + * printfs error messages. + * 2. getopt is NOT threadsafe! + * 3. This version of getopt() does not reset global variables until + * -1 is returned. As a result, your command line parsing loops + * must call getopt() repeatedly and continue to parse if other + * errors are returned ('?' or ':') until getopt() finally returns -1. + * (You can also set optind to -1 to force a reset). + * 4. Standard getopt() permutes the contents of argv as it scans, so that + * eventually all the nonoptions are at the end. This implementation + * does not do this. + * + * Returned Value: + * If an option was successfully found, then getopt() returns the option + * character. If all command-line options have been parsed, then getopt() + * returns -1. If getopt() encounters an option character that was not + * in optstring, then '?' is returned. If getopt() 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. + * + */ +int getopt_common(int argc, char * const argv[], + const char *optstring, + const struct option *longopts, + int *longindex, + enum getopt_mode_e mode) +{ + int ret; + /* Get thread-specific getopt() variables */ + + struct getopt_s *go = getoptvars(); + if (go == NULL) { + return '?'; + } + + /* Verify input parameters. */ + + if (argv != NULL) { + char *optchar; + int noarg_ret = '?'; + + /* The initial value of optind is 1. If getopt() is called again in + * the program, optind must be reset to some value <= 1. + */ + + if (go->go_optind < 1 || !go->go_binitialized) { + go->go_optarg = NULL; + go->go_optind = 1; /* Skip over the program name */ + go->go_optopt = '?'; + go->go_optptr = NULL; /* Start at the beginning of the first argument */ + go->go_binitialized = true; /* Now we are initialized */ + } + + /* Are we resuming in the middle, or at the end of a string of + * arguments? optptr == NULL means that we are started at the + * beginning of argv[optind]; *optptr == \0 means that we are + * starting at the beginning of optind+1 + */ + while (!go->go_optptr || !*go->go_optptr) { + /* We need to start at the beginning of the next argv. + * Check if we need to increment optind + */ + if (go->go_optptr) { + /* Yes.. Increment it and check for the case where where we + * have processed everything in the argv[] array. + */ + go->go_optind++; + } + + /* Check for the end of the argument list */ + go->go_optptr = argv[go->go_optind]; + if (!go->go_optptr) { + /* There are no more arguments, we are finished */ + go->go_binitialized = false; + return ERROR; + } + + /* We are starting at the beginning of argv[optind]. In this case, + * the first character must be '-' + */ + + if (*go->go_optptr != '-') { + /* The argument does not start with '-', we are finished */ + go->go_binitialized = false; + return ERROR; + } + /* Skip over the '-' */ + go->go_optptr++; + } + + /* Special case handling of "-" and "-:" */ + if (!*go->go_optptr) { + /* We'll fix up optptr the next time we are called */ + go->go_optopt = '\0'; + return '?'; + } + /* Handle the case of "-:" */ + if (*go->go_optptr == ':') { + go->go_optopt = ':'; + go->go_optptr++; + return '?'; + } + + /* go->go_optptr now points at the next option and it is not something + * crazy. Possibilities: + * + * FORM APPLICABILITY + * -o getopt(), getopt_long_only() + * -o reqarg getopt(), getopt_long_only() + * -o optarg getopt_long_only() + * -option getopt_long_only() + * -option reqarg getopt_long_only() + * -option optarg getopt_long_only() + * --option getopt_long(), getopt_long_only() + * --option reqarg getopt_long(), getopt_long_only() + * --option optarg getopt_long(), getopt_long_only() + * + * Where: + * o - Some short option + * option - Some long option + * reqarg - A required argument + * optarg - An optional argument + */ + + /* Check for --option forms or -option forms */ + if (GETOPT_HAVE_LONG(mode)) { + /* Handle -option and --option forms. */ + + if (*go->go_optptr == '-') { + /* Skip over the second '-' */ + + go->go_optptr++; + + /* And parse the long option */ + + ret = getopt_long_option(go, argv, longopts, longindex); + if (ret == '?') { + /* Skip over the unrecognized long option. */ + + go->go_optind++; + go->go_optptr = NULL; + } + + return ret; + } else if (GETOPT_HAVE_LONGONLY(mode)) { + /* The -option form is only valid in getop_long_only() mode and + * must be distinguished from the -o case forms. + * + * A special case is that the option is of a form like + * -o but is represented as a single character long option. + * In that case, getopt_long_option() will fail with '?' and, + * if it is a single character option, we can just fall + * through to the short option logic. + */ + ret = getopt_long_option(go, argv, longopts, longindex); + if (ret != '?') { + /* Return success or ERROR */ + return ret; + } else if (*(go->go_optptr + 1) != '\0') { + /* Check for single character option. + * + * REVISIT: There is no way to distinguish a sequence + * of short arguments like -abc (meaning -a -b -c) + * from a single long argument (like "abc"). I am not + * sure of the correct behavior in this case. + * While supported for getopt(), I do not think that + * the first interpretation is standard. + */ + + /* Skip over the unrecognized long option. */ + go->go_optind++; + go->go_optptr = NULL; + return ret; + } + } + } + /* Check if the option is in the list of valid short options. + * In long option modes, opstring may be NULL. However, that is + * an error in any case here because we have not found any + * long options. + */ + + if (optstring == NULL) { + /* Not an error with getopt_long() */ + if (GETOPT_HAVE_LONG(mode)) { + /* Return '?'. optptr is reset to the next argv entry, + * discarding everything else that follows in the argv string + * (which could be another single character command). + */ + + go->go_optopt = *go->go_optptr; + go->go_optptr = NULL; + go->go_optind++; + return '?'; + } else { + /* Restore the initial, uninitialized state, and return + * an error. + */ + + go->go_binitialized = false; + return ERROR; + } + } + + /* If the first character of opstring s ':', then ':' is in the event + * of a missing argument. Otherwise '?' is returned. + */ + if (*optstring == ':') { + noarg_ret = ':'; + optstring++; + } + + /* Check if the option appears in 'optstring' */ + + optchar = strchr(optstring, *go->go_optptr); + if (!optchar) { + /* No this character is not in the list of valid options */ + + go->go_optopt = *go->go_optptr; + go->go_optptr++; + return '?'; + } + + /* Yes, the character is in the list of valid options. Does it have a + * required argument? + */ + + if (optchar[1] != ':') { + /* No, no arguments. Just return the character that we found */ + go->go_optptr++; + return *optchar; + } + + /* Yes, it may have an argument. Is the argument immediately after + * the command in this same argument? + */ + + if (go->go_optptr[1] != '\0') { + /* Yes, return a pointer into the current argument */ + + go->go_optarg = &go->go_optptr[1]; + go->go_optind++; + go->go_optptr = NULL; + return *optchar; + } + + /* No.. is there an argument in the next value of argv[] ? */ + if (argv[go->go_optind + 1] && *argv[go->go_optind + 1] != '-') { + /* Yes.. return that */ + go->go_optarg = argv[go->go_optind + 1]; + go->go_optind += 2; + go->go_optptr = NULL; + return *optchar; + } + + /* No argument was supplied */ + go->go_optptr = NULL; + go->go_optarg = NULL; + go->go_optopt = *optchar; + go->go_optind++; + + /* Two colons means that the argument is optional. */ + return (optchar[2] == ':') ? *optchar : noarg_ret; + } + + /* Restore the initial, uninitialized state, and return an error. */ + + go->go_binitialized = false; + + /* Update global variables with currently processed getopt state */ + + return ERROR; +} diff --git a/lib/util/getopt/lib_getoptvars.c b/lib/util/getopt/lib_getoptvars.c new file mode 100644 index 00000000000000..6e3e740b62caee --- /dev/null +++ b/lib/util/getopt/lib_getoptvars.c @@ -0,0 +1,110 @@ +/**************************************************************************** + * + * SPDX-License-Identifier: Apache-2.0 + * + * libs/libc/unistd/lib_getoptvars.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#include +#include +#include "getopt_unistd.h" +#include + +/* Data is naturally process-specific in the KERNEL build so no special + * access to process-specific global data is needed. + */ + +static struct getopt_s g_getopt_vars = +{ + NULL, + 0, + 1, + '?', + NULL, + false +}; + +/* + * Name: getoptvars + * + * Description: + * Returns a pointer to to the thread-specific getopt() data. + * + */ + +struct getopt_s *getoptvars(void) +{/* + if (IS_ENABLED(CONFIG_SHELL_GETOPT)) { + k_tid_t tid = k_current_get(); + Z_STRUCT_SECTION_FOREACH(shell, sh) { + if (tid == sh->ctx->tid) { + return &sh->ctx->getopt_vars; + } + } + } +*/ + /* If not a shell thread return a common pointer */ + return &g_getopt_vars; +} + +void getoptvars_init(struct getopt_s *getopt_vars) +{ + optarg = NULL; + opterr = 0; + optind = 1; + optopt = '?'; + + if (getopt_vars == NULL) { + g_getopt_vars.go_optarg = NULL; + g_getopt_vars.go_opterr = 0; + g_getopt_vars.go_optind = 1; + g_getopt_vars.go_optopt = '?'; + g_getopt_vars.go_optptr = NULL; + g_getopt_vars.go_binitialized = false; + return; + } + + getopt_vars->go_optarg = NULL; + getopt_vars->go_opterr = 0; + getopt_vars->go_optind = 1; + getopt_vars->go_optopt = '?'; + getopt_vars->go_optptr = NULL; + getopt_vars->go_binitialized = false; +} + +struct getopt_s *getopt_state_get(void) +{ + return getoptvars(); +} + +void global_getopt_vars_set(struct getopt_s *go) +{ + if (go) { + optarg = go->go_optarg; + opterr = go->go_opterr; + optind = go->go_optind; + optopt = go->go_optopt; + } else { + optarg = g_getopt_vars.go_optarg; + opterr = g_getopt_vars.go_opterr; + optind = g_getopt_vars.go_optind; + optopt = g_getopt_vars.go_optopt; + } +} + diff --git a/samples/subsys/shell/shell_module/prj.conf b/samples/subsys/shell/shell_module/prj.conf index 2381918a41518d..0d12a90d55e410 100644 --- a/samples/subsys/shell/shell_module/prj.conf +++ b/samples/subsys/shell/shell_module/prj.conf @@ -12,3 +12,4 @@ CONFIG_POSIX_CLOCK=y CONFIG_DATE_SHELL=y CONFIG_THREAD_RUNTIME_STATS=y CONFIG_THREAD_RUNTIME_STATS_USE_TIMING_FUNCTIONS=y +CONFIG_SHELL_GETOPT=y diff --git a/samples/subsys/shell/shell_module/src/main.c b/samples/subsys/shell/shell_module/src/main.c index dcacdebbfa1902..85eb6c604e5946 100644 --- a/samples/subsys/shell/shell_module/src/main.c +++ b/samples/subsys/shell/shell_module/src/main.c @@ -92,14 +92,13 @@ static int cmd_demo_ping(const struct shell *shell, size_t argc, char **argv) #if defined CONFIG_SHELL_GETOPT static int cmd_demo_getopt(const struct shell *shell, size_t argc, char **argv) { - struct getopt_state *state; char *cvalue = NULL; int aflag = 0; 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) { + struct getopt_s *state = getopt_state_get(); switch (c) { case 'a': aflag = 1; @@ -108,7 +107,7 @@ static int cmd_demo_getopt(const struct shell *shell, size_t argc, char **argv) bflag = 1; break; case 'c': - cvalue = state->optarg; + cvalue = state->go_optarg; break; case 'h': /* When getopt is active shell is not parsing @@ -118,18 +117,18 @@ static int cmd_demo_getopt(const struct shell *shell, size_t argc, char **argv) shell_help(shell); return SHELL_CMD_HELP_PRINTED; case '?': - if (state->optopt == 'c') { + if (state->go_optopt == 'c') { shell_print(shell, "Option -%c requires an argument.", - state->optopt); - } else if (isprint(state->optopt)) { + state->go_optopt); + } else if (isprint(state->go_optopt)) { shell_print(shell, "Unknown option `-%c'.", - state->optopt); + state->go_optopt); } else { shell_print(shell, "Unknown option character `\\x%x'.", - state->optopt); + state->go_optopt); } return 1; default: diff --git a/subsys/shell/shell.c b/subsys/shell/shell.c index c71f54e6844c7b..977a2b416f9e3e 100644 --- a/subsys/shell/shell.c +++ b/subsys/shell/shell.c @@ -538,7 +538,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); + z_shell_getopt_init(&shell->ctx->getopt_vars); #endif z_flag_cmd_ctx_set(shell, true); diff --git a/subsys/shell/shell_getopt.c b/subsys/shell/shell_getopt.c index 9b1dea144635ec..032e6387a14384 100644 --- a/subsys/shell/shell_getopt.c +++ b/subsys/shell/shell_getopt.c @@ -8,30 +8,12 @@ #include #include -void z_shell_getopt_init(struct getopt_state *state) +void z_shell_getopt_init(struct getopt_s *vars) { - getopt_init(state); + getoptvars_init(vars); } -int shell_getopt(const struct shell *shell, int argc, char *const argv[], - const char *ostr) +struct getopt_s *shell_getopt_state_get(const struct shell *shell) { - 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; + return &shell->ctx->getopt_vars; } diff --git a/tests/subsys/shell/shell_getopt/CMakeLists.txt b/tests/subsys/shell/shell_getopt/CMakeLists.txt new file mode 100644 index 00000000000000..c1b649ce76cf59 --- /dev/null +++ b/tests/subsys/shell/shell_getopt/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(shell) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/subsys/shell/shell_getopt/prj.conf b/tests/subsys/shell/shell_getopt/prj.conf new file mode 100644 index 00000000000000..503a7746127791 --- /dev/null +++ b/tests/subsys/shell/shell_getopt/prj.conf @@ -0,0 +1,11 @@ +CONFIG_SHELL=y +CONFIG_SHELL_GETOPT=y +CONFIG_SHELL_BACKEND_SERIAL=n +CONFIG_SHELL_BACKEND_DUMMY=y +CONFIG_SHELL_CMDS_SELECT=y +CONFIG_SHELL_CMD_BUFF_SIZE=90 +CONFIG_SHELL_PRINTF_BUFF_SIZE=15 +CONFIG_SHELL_METAKEYS=n +CONFIG_LOG=n +CONFIG_ZTEST=y +CONFIG_TEST_LOGGING_DEFAULTS=n diff --git a/tests/subsys/shell/shell_getopt/src/main.c b/tests/subsys/shell/shell_getopt/src/main.c new file mode 100644 index 00000000000000..12f0ebf590573f --- /dev/null +++ b/tests/subsys/shell/shell_getopt/src/main.c @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file + * @brief Interactive shell test suite + * + */ + +#include +#include +#include + +#include +#include + +static void test_getopt_basic(void) +{ + static const char *const nargv[] = { + "cmd_name", + "-b", + "-a", + "-h", + "-c", + "-l", + "-h", + "-a", + "-i", + "-w", + }; + static const char *accepted_opt = "abchw"; + static const char *expected = "bahc?ha?w"; + size_t argc = ARRAY_SIZE(nargv); + int cnt = 0; + int c; + char **argv; + + argv = (char **)nargv; + + getoptvars_init(NULL); + + do { + c = getopt(argc, argv, accepted_opt); + if (cnt >= strlen(expected)) { + break; + } + + zassert_equal(c, expected[cnt++], "unexpected opt character"); + } while (c != -1); +} + +enum getopt_idx { + GETOPT_IDX_CMD_NAME, + GETOPT_IDX_OPTION1, + GETOPT_IDX_OPTION2, + GETOPT_IDX_OPTARG +}; + +static void test_getopt(void) +{ + static const char *test_opts = "ac:"; + static const char *const nargv[] = { + [GETOPT_IDX_CMD_NAME] = "cmd_name", + [GETOPT_IDX_OPTION1] = "-a", + [GETOPT_IDX_OPTION2] = "-c", + [GETOPT_IDX_OPTARG] = "foo", + }; + int argc = ARRAY_SIZE(nargv); + char **argv; + int c; + + argv = (char **)nargv; + + getoptvars_init(NULL); + + /* Test uknown option */ + c = getopt(argc, argv, test_opts); + + zassert_equal(c, 'a', "unexpected opt character"); + c = getopt(argc, argv, test_opts); + zassert_equal(c, 'c', "unexpected opt character"); + c = getopt(argc, argv, test_opts); + zassert_equal(0, strcmp(argv[GETOPT_IDX_OPTARG], optarg), + "unexpected optarg result"); +} + +enum getopt_long_idx { + GETOPT_LONG_IDX_CMD_NAME, + GETOPT_LONG_IDX_VERBOSE, + GETOPT_LONG_IDX_OPT, + GETOPT_LONG_IDX_OPTARG +}; + +static void test_getopt_long(void) +{ + /* Below test is based on example + * https://www.gnu.org/software/libc/manual/html_node/Getopt-Long-Option-Example.html + */ + int verbose_flag = 0; + /* getopt_long stores the option index here. */ + int option_index = 0; + char **argv; + int c; + struct option long_options[] = { + /* These options set a flag. */ + {"verbose", no_argument, &verbose_flag, 1}, + {"brief", no_argument, &verbose_flag, 0}, + /* These options don’t set a flag. + * We distinguish them by their indices. + */ + {"add", no_argument, 0, 'a'}, + {"create", required_argument, 0, 'c'}, + {"delete", required_argument, 0, 'd'}, + {"long", required_argument, 0, 'e'}, + {0, 0, 0, 0} + }; + static const char *accepted_opt = "ac:d:e:"; + + static const char *const argv1[] = { + [GETOPT_LONG_IDX_CMD_NAME] = "cmd_name", + [GETOPT_LONG_IDX_VERBOSE] = "--verbose", + [GETOPT_LONG_IDX_OPT] = "--create", + [GETOPT_LONG_IDX_OPTARG] = "some_file", + }; + int argc1 = ARRAY_SIZE(argv1); + + static const char *const argv2[] = { + [GETOPT_LONG_IDX_CMD_NAME] = "cmd_name", + [GETOPT_LONG_IDX_VERBOSE] = "--brief", + [GETOPT_LONG_IDX_OPT] = "-d", + [GETOPT_LONG_IDX_OPTARG] = "other_file", + }; + int argc2 = ARRAY_SIZE(argv2); + + static const char *const argv3[] = { + [GETOPT_LONG_IDX_CMD_NAME] = "cmd_name", + [GETOPT_LONG_IDX_VERBOSE] = "--brief", + [GETOPT_LONG_IDX_OPT] = "-a", + }; + int argc3 = ARRAY_SIZE(argv3); + + /* this test distinguish getopt_long and getopt_long_only functions */ + static const char *const argv4[] = { + [GETOPT_LONG_IDX_CMD_NAME] = "cmd_name", + [GETOPT_LONG_IDX_VERBOSE] = "--brief", + /* below should not be interpreted as "--long/-e" option */ + [GETOPT_LONG_IDX_OPT] = "-l", + [GETOPT_LONG_IDX_OPTARG] = "long_argument", + }; + int argc4 = ARRAY_SIZE(argv4); + + /* Test scenario 1 */ + argv = (char **)argv1; + getoptvars_init(NULL); + c = getopt_long(argc1, argv, accepted_opt, + long_options, &option_index); + printf("verbose flag = %d\n", verbose_flag); + zassert_equal(verbose_flag, 1, "verbose flag expected"); + return; + c = getopt_long(argc1, argv, accepted_opt, + long_options, &option_index); + return; + zassert_equal('c', c, "unexpected option"); + + zassert_equal(0, strcmp(optarg, argv[GETOPT_LONG_IDX_OPTARG]), + "unexpected optarg"); + c = getopt_long(argc1, argv, accepted_opt, + long_options, &option_index); + zassert_equal(-1, c, "shell_getopt_long shall return -1"); + + return; + /* Test scenario 2 */ + argv = (char **)argv2; + getoptvars_init(NULL); + c = getopt_long(argc2, argv, accepted_opt, + long_options, &option_index); + zassert_equal(verbose_flag, 0, "verbose flag expected"); + c = getopt_long(argc2, argv, accepted_opt, + long_options, &option_index); + zassert_equal('d', c, "unexpected option"); + zassert_equal(0, strcmp(optarg, argv[GETOPT_LONG_IDX_OPTARG]), + "unexpected optarg"); + c = getopt_long(argc2, argv, accepted_opt, + long_options, &option_index); + zassert_equal(-1, c, "shell_getopt_long shall return -1"); + + /* Test scenario 3 */ + argv = (char **)argv3; + getoptvars_init(NULL); + c = getopt_long(argc3, argv, accepted_opt, + long_options, &option_index); + zassert_equal(verbose_flag, 0, "verbose flag expected"); + c = getopt_long(argc3, argv, accepted_opt, + long_options, &option_index); + zassert_equal('a', c, "unexpected option"); + c = getopt_long(argc3, argv, accepted_opt, + long_options, &option_index); + zassert_equal(-1, c, "shell_getopt_long shall return -1"); + + /* Test scenario 4 */ + argv = (char **)argv4; + getoptvars_init(NULL); + c = getopt_long(argc4, argv, accepted_opt, + long_options, &option_index); + zassert_equal(verbose_flag, 0, "verbose flag expected"); + c = getopt_long(argc4, argv, accepted_opt, + long_options, &option_index); + + /* Function was called with option '-l'. It is expected it will be + * NOT evaluated to '--long' which has flag 'e'. + */ + zassert_not_equal('e', c, "unexpected option match"); + c = getopt_long(argc4, argv, accepted_opt, + long_options, &option_index); +} + +static void test_getopt_long_only(void) +{ + /* Below test is based on example + * https://www.gnu.org/software/libc/manual/html_node/Getopt-Long-Option-Example.html + */ + int verbose_flag = 0; + /* getopt_long stores the option index here. */ + int option_index = 0; + char **argv; + int c; + struct option long_options[] = { + /* These options set a flag. */ + {"verbose", no_argument, &verbose_flag, 1}, + {"brief", no_argument, &verbose_flag, 0}, + /* These options don’t set a flag. + * We distinguish them by their indices. + */ + {"add", no_argument, 0, 'a'}, + {"create", required_argument, 0, 'c'}, + {"delete", required_argument, 0, 'd'}, + {"long", required_argument, 0, 'e'}, + {0, 0, 0, 0} + }; + static const char *accepted_opt = "ac:d:e:"; + + static const char *const argv1[] = { + [GETOPT_LONG_IDX_CMD_NAME] = "cmd_name", + [GETOPT_LONG_IDX_VERBOSE] = "--verbose", + [GETOPT_LONG_IDX_OPT] = "--create", + [GETOPT_LONG_IDX_OPTARG] = "some_file", + }; + int argc1 = ARRAY_SIZE(argv1); + + static const char *const argv2[] = { + [GETOPT_LONG_IDX_CMD_NAME] = "cmd_name", + [GETOPT_LONG_IDX_VERBOSE] = "--brief", + [GETOPT_LONG_IDX_OPT] = "-d", + [GETOPT_LONG_IDX_OPTARG] = "other_file", + }; + int argc2 = ARRAY_SIZE(argv2); + + static const char *const argv3[] = { + [GETOPT_LONG_IDX_CMD_NAME] = "cmd_name", + [GETOPT_LONG_IDX_VERBOSE] = "--brief", + [GETOPT_LONG_IDX_OPT] = "-a", + }; + int argc3 = ARRAY_SIZE(argv3); + + /* this test distinguish getopt_long and getopt_long_only functions */ + static const char *const argv4[] = { + [GETOPT_LONG_IDX_CMD_NAME] = "cmd_name", + [GETOPT_LONG_IDX_VERBOSE] = "--brief", + /* below should be interpreted as "--long/-e" option */ + [GETOPT_LONG_IDX_OPT] = "-l", + [GETOPT_LONG_IDX_OPTARG] = "long_argument", + }; + int argc4 = ARRAY_SIZE(argv4); + + /* Test scenario 1 */ + argv = (char **)argv1; + getoptvars_init(NULL); + c = getopt_long_only(argc1, argv, accepted_opt, + long_options, &option_index); + zassert_equal(verbose_flag, 1, "verbose flag expected"); + c = getopt_long_only(argc1, argv, accepted_opt, + long_options, &option_index); + zassert_equal('c', c, "unexpected option"); + zassert_equal(0, strcmp(optarg, argv[GETOPT_LONG_IDX_OPTARG]), + "unexpected optarg"); + c = getopt_long_only(argc1, argv, accepted_opt, + long_options, &option_index); + zassert_equal(-1, c, "shell_getopt_long_only shall return -1"); + + /* Test scenario 2 */ + argv = (char **)argv2; + getoptvars_init(NULL); + c = getopt_long_only(argc2, argv, accepted_opt, + long_options, &option_index); + zassert_equal(verbose_flag, 0, "verbose flag expected"); + c = getopt_long_only(argc2, argv, accepted_opt, + long_options, &option_index); + zassert_equal('d', c, "unexpected option"); + zassert_equal(0, strcmp(optarg, argv[GETOPT_LONG_IDX_OPTARG]), + "unexpected optarg"); + c = getopt_long_only(argc2, argv, accepted_opt, + long_options, &option_index); + zassert_equal(-1, c, "shell_getopt_long_only shall return -1"); + + /* Test scenario 3 */ + argv = (char **)argv3; + getoptvars_init(NULL); + c = getopt_long_only(argc3, argv, accepted_opt, + long_options, &option_index); + zassert_equal(verbose_flag, 0, "verbose flag expected"); + c = getopt_long_only(argc3, argv, accepted_opt, + long_options, &option_index); + zassert_equal('a', c, "unexpected option"); + c = getopt_long_only(argc3, argv, accepted_opt, + long_options, &option_index); + zassert_equal(-1, c, "shell_getopt_long_only shall return -1"); + + /* Test scenario 4 */ + argv = (char **)argv4; + getoptvars_init(NULL); + c = getopt_long_only(argc4, argv, accepted_opt, + long_options, &option_index); + zassert_equal(verbose_flag, 0, "verbose flag expected"); + c = getopt_long_only(argc4, argv, accepted_opt, + long_options, &option_index); + + /* Function was called with option '-l'. It is expected it will be + * evaluated to '--long' which has flag 'e'. + */ + zassert_equal('e', c, "unexpected option"); + c = getopt_long_only(argc4, argv, accepted_opt, + long_options, &option_index); +} + +void test_main(void) +{ + ztest_test_suite(shell_test_suite, + ztest_unit_test(test_getopt_basic), + ztest_unit_test(test_getopt), + ztest_unit_test(test_getopt_long), + ztest_unit_test(test_getopt_long_only) + ); + + ztest_run_test_suite(shell_test_suite); +} diff --git a/tests/subsys/shell/shell_getopt/testcase.yaml b/tests/subsys/shell/shell_getopt/testcase.yaml new file mode 100644 index 00000000000000..4213c4288f9487 --- /dev/null +++ b/tests/subsys/shell/shell_getopt/testcase.yaml @@ -0,0 +1,8 @@ +tests: + shell.getopt: + integration_platforms: + - native_posix + min_flash: 64 + min_ram: 32 + filter: ( CONFIG_SHELL ) + tags: flash shell