Skip to content

Commit 8386de9

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. Signed-off-by: Keith Packard <keithp@keithp.com>
1 parent 9c1fbc8 commit 8386de9

File tree

16 files changed

+385
-415
lines changed

16 files changed

+385
-415
lines changed

doc/services/shell/index.rst

Lines changed: 12 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,21 @@ 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) {
721+
state = getopt_data_get();
720722
switch (c) {
721723
case 'c':
722-
cvalue = state->optarg;
724+
cvalue = state.optarg;
723725
break;
724726
default:
725727
break;
726728
}
727729
}
728730
729-
Thread safe getopt functionality is activated by
730-
:kconfig:option:`CONFIG_SHELL_GETOPT` set to ``y``.
731+
Thread safe getopt functionality is enabled by defining `__need_getopt_newlib`
732+
before including getopt.h
733+
731734

732735
Obscured Input Feature
733736
**********************

include/zephyr/shell/shell.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <zephyr/toolchain.h>
2121

2222
#if defined CONFIG_SHELL_GETOPT
23+
#define __need_getopt_newlib
2324
#include <getopt.h>
2425
#endif
2526

@@ -995,7 +996,7 @@ struct shell_ctx {
995996

996997
#if defined CONFIG_SHELL_GETOPT
997998
/*!< getopt context for a shell backend. */
998-
struct getopt_state getopt;
999+
struct getopt_data getopt;
9991000
#endif
10001001

10011002
uint16_t cmd_buff_len; /*!< Command length.*/

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: 16 additions & 39 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,27 @@ 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();
85-
8655
if (state->optreset || *state->place == 0) { /* update scanning pointer */
8756
state->optreset = 0;
8857
state->place = nargv[state->optind];
8958
if (state->optind >= nargc || *state->place++ != '-') {
9059
/* Argument is absent or is not an option */
9160
state->place = EMSG;
92-
z_getopt_global_state_update(state);
9361
return -1;
9462
}
9563
state->optopt = *state->place++;
9664
if (state->optopt == '-' && *state->place == 0) {
9765
/* "--" => end of options */
9866
++state->optind;
9967
state->place = EMSG;
100-
z_getopt_global_state_update(state);
10168
return -1;
10269
}
10370
if (state->optopt == 0) {
@@ -106,7 +73,6 @@ int getopt(int nargc, char *const nargv[], const char *ostr)
10673
*/
10774
state->place = EMSG;
10875
if (strchr(ostr, '-') == NULL) {
109-
z_getopt_global_state_update(state);
11076
return -1;
11177
}
11278
state->optopt = '-';
@@ -124,7 +90,6 @@ int getopt(int nargc, char *const nargv[], const char *ostr)
12490
if (state->opterr && *ostr != ':') {
12591
LOG_DBG("illegal option -- %c", state->optopt);
12692
}
127-
z_getopt_global_state_update(state);
12893
return BADCH;
12994
}
13095

@@ -147,18 +112,30 @@ int getopt(int nargc, char *const nargv[], const char *ostr)
147112
/* option-argument absent */
148113
state->place = EMSG;
149114
if (*ostr == ':') {
150-
z_getopt_global_state_update(state);
151115
return BADARG;
152116
}
153117
if (state->opterr) {
154118
LOG_DBG("option requires an argument -- %c", state->optopt);
155119
}
156-
z_getopt_global_state_update(state);
157120
return BADCH;
158121
}
159122
state->place = EMSG;
160123
++state->optind;
161124
}
162-
z_getopt_global_state_update(state);
163125
return state->optopt; /* return option letter */
164126
}
127+
128+
/*
129+
* getopt --
130+
* Parse argc/argv argument vector.
131+
*/
132+
int getopt(int nargc, char *const nargv[], const char *ostr)
133+
{
134+
struct getopt_data state;
135+
int ret;
136+
137+
z_getopt_global_state_get(&state);
138+
ret = __getopt_r(nargc, nargv, ostr, &state);
139+
z_getopt_global_state_put(&state);
140+
return ret;
141+
}

lib/posix/c_lib_ext/getopt/getopt.h

Lines changed: 40 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,27 @@ 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+
#if CONFIG_GETOPT_LONG
38+
#define GETOPT_DATA_INITIALIZER { 1, 1, 0, 0, NULL, "", -1, -1 }
39+
#else
40+
#define GETOPT_DATA_INITIALIZER { 1, 1, 0, 0, NULL, "" }
41+
#endif
42+
43+
#endif
44+
45+
#if defined(__need_getopt_legacy) || !defined(__need_getopt_newlib)
46+
/* Applications using the newlib-style APIs "shouldn't" be using these variables */
3147
extern int optreset; /* reset getopt */
3248
extern char *optarg;
3349
extern int opterr;
3450
extern int optind;
3551
extern int optopt;
52+
#endif
3653

3754
#define no_argument 0
3855
#define required_argument 1
@@ -52,12 +69,6 @@ struct option {
5269
int val;
5370
};
5471

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-
6172
/**
6273
* @brief Parses the command-line arguments.
6374
*
@@ -108,6 +119,28 @@ int getopt_long(int nargc, char *const *nargv, const char *options,
108119
int getopt_long_only(int nargc, char *const *nargv, const char *options,
109120
const struct option *long_options, int *idx);
110121

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

lib/posix/c_lib_ext/getopt/getopt_common.c

Lines changed: 12 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,45 +3,24 @@
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
*/
1914
int opterr = 1; /* if error message should be printed */
2015
int optind = 1; /* 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-
35-
#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) */
38-
#endif
39-
};
40-
4120
/* This function is not thread safe. All threads using getopt are calling
4221
* this function.
4322
*/
44-
void z_getopt_global_state_update(struct getopt_state *state)
23+
void z_getopt_global_state_put(struct getopt_data *state)
4524
{
4625
opterr = state->opterr;
4726
optind = state->optind;
@@ -50,19 +29,12 @@ void z_getopt_global_state_update(struct getopt_state *state)
5029
optarg = state->optarg;
5130
}
5231

53-
/* It is internal getopt API function, it shall not be called by the user. */
54-
struct getopt_state *getopt_state_get(void)
32+
/* Place the global state into a struct */
33+
void z_getopt_global_state_get(struct getopt_data *state)
5534
{
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-
}
65-
#endif
66-
/* If not a shell thread return a common pointer */
67-
return &m_getopt_common_state;
35+
state->opterr = opterr;
36+
state->optind = optind;
37+
state->optopt = optopt;
38+
state->optreset = optreset;
39+
state->optarg = optarg;
6840
}

lib/posix/c_lib_ext/getopt/getopt_common.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,21 @@
77
#ifndef _GETOPT_COMMON_H__
88
#define _GETOPT_COMMON_H__
99

10+
#define __need_getopt_newlib
11+
#include "getopt.h"
12+
1013
#ifdef __cplusplus
1114
extern "C" {
1215
#endif
1316

14-
/* This function is not thread safe. All threads using getopt are calling
15-
* this function.
17+
/* These functions are not thread safe. All threads using getopt are calling
18+
* them.
1619
*/
17-
void z_getopt_global_state_update(struct getopt_state *state);
20+
/* Put state into POSIX global variables */
21+
void z_getopt_global_state_put(struct getopt_data *state);
22+
23+
/* Get state from POSIX global variables */
24+
void z_getopt_global_state_get(struct getopt_data *state);
1825

1926
#ifdef __cplusplus
2027
}

0 commit comments

Comments
 (0)