Skip to content

Commit 7758f98

Browse files
committed
lib/posix: Adopt newlib-style re-entrant API for getopt family
Replace the Zephyr-specific re-entrant getopt family APIs with those from newlib. This allows use of the newlib and picolibc native implementations and provides a cleaner re-entrant mechanism, using a local variable to hold the getopt state instead of having a thread-local variabled tucked away in the shell context. This will require changing all re-entrant users of getopt, switching from the Zephyr-style interfaces to the newlib-style. This also adopts the glibc/picolibc convention where setting optind = 0 causes the getopt functions to re-initialize all parsing state. This lets us test the non-reentrant API without needing getopt_init(). Signed-off-by: Keith Packard <keithp@keithp.com>
1 parent 9c1fbc8 commit 7758f98

File tree

18 files changed

+710
-428
lines changed

18 files changed

+710
-428
lines changed

doc/services/shell/index.rst

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -690,9 +690,11 @@ in the FreeBSD project. This feature is activated by:
690690
:kconfig:option:`CONFIG_POSIX_C_LIB_EXT` set to ``y`` and :kconfig:option:`CONFIG_GETOPT_LONG`
691691
set to ``y``.
692692

693-
This feature can be used in thread safe as well as non thread safe manner.
694-
The former is full compatible with regular getopt usage while the latter
695-
a bit differs.
693+
This feature can be used in thread safe as well as non thread safe
694+
manner. The former is full compatible with regular getopt usage while
695+
the latter differs a bit. To gain access to the thread-safe APIs, add
696+
`#define __need_getopt_newlib` before `#include <getopt.h>` in your
697+
source code.
696698

697699
An example non-thread safe usage:
698700

@@ -714,20 +716,20 @@ An example thread safe usage:
714716
.. code-block:: c
715717
716718
char *cvalue = NULL;
717-
struct getopt_state *state;
718-
while ((char c = getopt(argc, argv, "abhc:")) != -1) {
719-
state = getopt_state_get();
719+
struct getopt_data state = GETOPT_DATA_INITIALIZER;
720+
while ((char c = getopt_r(argc, argv, "abhc:", &state)) != -1) {
720721
switch (c) {
721722
case 'c':
722-
cvalue = state->optarg;
723+
cvalue = state.optarg;
723724
break;
724725
default:
725726
break;
726727
}
727728
}
728729
729-
Thread safe getopt functionality is activated by
730-
:kconfig:option:`CONFIG_SHELL_GETOPT` set to ``y``.
730+
Thread safe getopt functionality is enabled by defining `__need_getopt_newlib`
731+
before including getopt.h
732+
731733

732734
Obscured Input Feature
733735
**********************

include/zephyr/shell/shell.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
#include <zephyr/toolchain.h>
2121

2222
#if defined CONFIG_SHELL_GETOPT
23+
/*
24+
* All shell commands should use the re-entrant APIs provided by the
25+
* newlib-compatible getopt APIs. This exposes those and hides the
26+
* globals used by the non-reentrant APIs.
27+
*/
28+
#define __need_getopt_newlib
2329
#include <getopt.h>
2430
#endif
2531

@@ -993,11 +999,6 @@ struct shell_ctx {
993999
/*!< Logging level for a backend. */
9941000
uint32_t log_level;
9951001

996-
#if defined CONFIG_SHELL_GETOPT
997-
/*!< getopt context for a shell backend. */
998-
struct getopt_state getopt;
999-
#endif
1000-
10011002
uint16_t cmd_buff_len; /*!< Command length.*/
10021003
uint16_t cmd_buff_pos; /*!< Command buffer cursor position.*/
10031004

lib/posix/c_lib_ext/Kconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ config GETOPT_LONG
2323
not interfere with each other.
2424
All not shell threads share one global instance of getopt state, hence
2525
apart from shell this library is not thread safe. User can add support
26-
for other threads by extending function getopt_state_get in
26+
for other threads by extending function getopt_data_get in
2727
getopt_common.c file.
2828

2929
endif # POSIX_C_LIB_EXT

lib/posix/c_lib_ext/getopt/getopt.c

Lines changed: 29 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
#else
3636
#include <zephyr/posix/unistd.h>
3737
#endif
38-
#include "getopt.h"
3938
#include "getopt_common.h"
4039

4140
#include <zephyr/logging/log.h>
@@ -45,59 +44,41 @@ LOG_MODULE_REGISTER(getopt);
4544
#define BADARG ((int)':')
4645
#define EMSG ""
4746

48-
void getopt_init(void)
49-
{
50-
struct getopt_state *state;
51-
52-
state = getopt_state_get();
53-
54-
state->opterr = 1;
55-
state->optind = 1;
56-
state->optopt = 0;
57-
state->optreset = 0;
58-
state->optarg = NULL;
59-
60-
state->place = ""; /* EMSG */
61-
62-
#if CONFIG_GETOPT_LONG
63-
state->nonopt_start = -1; /* first non option argument (for permute) */
64-
state->nonopt_end = -1; /* first option after non options (for permute) */
65-
#endif
66-
67-
opterr = 1;
68-
optind = 1;
69-
optopt = 0;
70-
optreset = 0;
71-
optarg = NULL;
72-
}
73-
7447
/*
7548
* getopt --
7649
* Parse argc/argv argument vector.
7750
*/
78-
int getopt(int nargc, char *const nargv[], const char *ostr)
51+
int __getopt_r(int nargc, char *const nargv[], const char *ostr, struct getopt_data *state)
7952
{
80-
struct getopt_state *state;
8153
char *oli; /* option letter list index */
8254

83-
/* get getopt state of the current thread */
84-
state = getopt_state_get();
55+
/* glibc/picolibc compatibility -- reset if optind is 0 */
56+
if (state->optind == 0) {
57+
state->opterr = 1;
58+
state->optind = 1;
59+
state->optopt = 0;
60+
state->optreset = 0;
61+
state->optarg = NULL;
62+
state->place = "";
63+
#if CONFIG_GETOPT_LONG
64+
state->nonopt_start = -1;
65+
state->nonopt_end = -1;
66+
#endif
67+
}
8568

8669
if (state->optreset || *state->place == 0) { /* update scanning pointer */
8770
state->optreset = 0;
8871
state->place = nargv[state->optind];
8972
if (state->optind >= nargc || *state->place++ != '-') {
9073
/* Argument is absent or is not an option */
9174
state->place = EMSG;
92-
z_getopt_global_state_update(state);
9375
return -1;
9476
}
9577
state->optopt = *state->place++;
9678
if (state->optopt == '-' && *state->place == 0) {
9779
/* "--" => end of options */
9880
++state->optind;
9981
state->place = EMSG;
100-
z_getopt_global_state_update(state);
10182
return -1;
10283
}
10384
if (state->optopt == 0) {
@@ -106,7 +87,6 @@ int getopt(int nargc, char *const nargv[], const char *ostr)
10687
*/
10788
state->place = EMSG;
10889
if (strchr(ostr, '-') == NULL) {
109-
z_getopt_global_state_update(state);
11090
return -1;
11191
}
11292
state->optopt = '-';
@@ -124,7 +104,6 @@ int getopt(int nargc, char *const nargv[], const char *ostr)
124104
if (state->opterr && *ostr != ':') {
125105
LOG_DBG("illegal option -- %c", state->optopt);
126106
}
127-
z_getopt_global_state_update(state);
128107
return BADCH;
129108
}
130109

@@ -147,18 +126,30 @@ int getopt(int nargc, char *const nargv[], const char *ostr)
147126
/* option-argument absent */
148127
state->place = EMSG;
149128
if (*ostr == ':') {
150-
z_getopt_global_state_update(state);
151129
return BADARG;
152130
}
153131
if (state->opterr) {
154132
LOG_DBG("option requires an argument -- %c", state->optopt);
155133
}
156-
z_getopt_global_state_update(state);
157134
return BADCH;
158135
}
159136
state->place = EMSG;
160137
++state->optind;
161138
}
162-
z_getopt_global_state_update(state);
163139
return state->optopt; /* return option letter */
164140
}
141+
142+
/*
143+
* getopt --
144+
* Parse argc/argv argument vector.
145+
*/
146+
int getopt(int nargc, char *const nargv[], const char *ostr)
147+
{
148+
struct getopt_data state;
149+
int ret;
150+
151+
z_getopt_global_state_get(&state);
152+
ret = __getopt_r(nargc, nargv, ostr, &state);
153+
z_getopt_global_state_put(&state);
154+
return ret;
155+
}

lib/posix/c_lib_ext/getopt/getopt.h

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ extern "C" {
1313

1414
#include <zephyr/kernel.h>
1515

16-
struct getopt_state {
16+
#ifdef __need_getopt_newlib
17+
struct getopt_data {
1718
int opterr; /* if error message should be printed */
1819
int optind; /* index into parent argv vector */
1920
int optopt; /* character checked for validity */
@@ -28,11 +29,23 @@ struct getopt_state {
2829
#endif
2930
};
3031

32+
/*
33+
* The GETOPT_DATA_INITIALIZER macro is used to initialize a statically-
34+
* allocated variable of type struct getopt_data.
35+
*/
36+
37+
#define GETOPT_DATA_INITIALIZER { 0 }
38+
39+
#endif
40+
41+
#if defined(__need_getopt_legacy) || !defined(__need_getopt_newlib)
42+
/* Applications using the newlib-style APIs "shouldn't" be using these variables */
3143
extern int optreset; /* reset getopt */
3244
extern char *optarg;
3345
extern int opterr;
3446
extern int optind;
3547
extern int optopt;
48+
#endif
3649

3750
#define no_argument 0
3851
#define required_argument 1
@@ -52,12 +65,6 @@ struct option {
5265
int val;
5366
};
5467

55-
/* Function initializes getopt_state structure for current thread */
56-
void getopt_init(void);
57-
58-
/* Function returns getopt_state structure for the current thread. */
59-
struct getopt_state *getopt_state_get(void);
60-
6168
/**
6269
* @brief Parses the command-line arguments.
6370
*
@@ -108,6 +115,28 @@ int getopt_long(int nargc, char *const *nargv, const char *options,
108115
int getopt_long_only(int nargc, char *const *nargv, const char *options,
109116
const struct option *long_options, int *idx);
110117

118+
/* Re-entrant forms of the above functions. These follow the newlib/picolibc API */
119+
120+
#ifdef __need_getopt_newlib
121+
122+
/* These #defines are to make accessing the reentrant functions easier. */
123+
#define getopt_r __getopt_r
124+
#define getopt_long_r __getopt_long_r
125+
#define getopt_long_only_r __getopt_long_only_r
126+
127+
int __getopt_r(int __argc, char *const __argv[], const char *__optstring,
128+
struct getopt_data *__data);
129+
130+
int __getopt_long_r(int __argc, char *const __argv[], const char *__shortopts,
131+
const struct option *__longopts, int *__longind,
132+
struct getopt_data *__data);
133+
134+
int __getopt_long_only_r(int __argc, char *const __argv[], const char *__shortopts,
135+
const struct option *__longopts, int *__longind,
136+
struct getopt_data *__data);
137+
138+
#endif /* __need_getopt_newlib */
139+
111140
#ifdef __cplusplus
112141
}
113142
#endif

lib/posix/c_lib_ext/getopt/getopt_common.c

Lines changed: 26 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,66 +3,54 @@
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
6-
#include <zephyr/kernel.h>
7-
#include <zephyr/shell/shell.h>
8-
#if CONFIG_SHELL_GETOPT
9-
#include <zephyr/sys/iterable_sections.h>
10-
#endif
11-
12-
#include "getopt.h"
6+
#define __need_getopt_legacy
7+
#include "getopt_common.h"
138

149
/* Referring below variables is not thread safe. They reflects getopt state
1510
* only when 1 thread is using getopt.
16-
* When more threads are using getopt please call getopt_state_get to know
17-
* getopt state for the current thread.
11+
* When more threads are using getopt please use getopt_r and friends
12+
* to hold the getopt state in local variables
1813
*/
19-
int opterr = 1; /* if error message should be printed */
20-
int optind = 1; /* index into parent argv vector */
14+
int opterr; /* if error message should be printed */
15+
int optind; /* index into parent argv vector */
2116
int optopt; /* character checked for validity */
2217
int optreset; /* reset getopt */
2318
char *optarg; /* argument associated with option */
2419

25-
/* Common state for all threads that did not have own getopt state. */
26-
static struct getopt_state m_getopt_common_state = {
27-
.opterr = 1,
28-
.optind = 1,
29-
.optopt = 0,
30-
.optreset = 0,
31-
.optarg = NULL,
32-
33-
.place = "", /* EMSG */
34-
20+
static char *place;
3521
#if CONFIG_GETOPT_LONG
36-
.nonopt_start = -1, /* first non option argument (for permute) */
37-
.nonopt_end = -1, /* first option after non options (for permute) */
22+
static int nonopt_start;
23+
static int nonopt_end;
3824
#endif
39-
};
4025

4126
/* This function is not thread safe. All threads using getopt are calling
4227
* this function.
4328
*/
44-
void z_getopt_global_state_update(struct getopt_state *state)
29+
void z_getopt_global_state_put(struct getopt_data *state)
4530
{
4631
opterr = state->opterr;
4732
optind = state->optind;
4833
optopt = state->optopt;
4934
optreset = state->optreset;
5035
optarg = state->optarg;
36+
place = state->place;
37+
#if CONFIG_GETOPT_LONG
38+
nonopt_start = state->nonopt_start;
39+
nonopt_end = state->nonopt_end;
40+
#endif
5141
}
5242

53-
/* It is internal getopt API function, it shall not be called by the user. */
54-
struct getopt_state *getopt_state_get(void)
43+
/* Place the global state into a struct */
44+
void z_getopt_global_state_get(struct getopt_data *state)
5545
{
56-
#if CONFIG_SHELL_GETOPT
57-
k_tid_t tid;
58-
59-
tid = k_current_get();
60-
STRUCT_SECTION_FOREACH(shell, sh) {
61-
if (tid == sh->ctx->tid) {
62-
return &sh->ctx->getopt;
63-
}
64-
}
46+
state->opterr = opterr;
47+
state->optind = optind;
48+
state->optopt = optopt;
49+
state->optreset = optreset;
50+
state->optarg = optarg;
51+
state->place = place;
52+
#if CONFIG_GETOPT_LONG
53+
state->nonopt_start = nonopt_start;
54+
state->nonopt_end = nonopt_end;
6555
#endif
66-
/* If not a shell thread return a common pointer */
67-
return &m_getopt_common_state;
6856
}

0 commit comments

Comments
 (0)