Skip to content

posix: env: support for environ, getenv(), setenv(), unsetenv() #66762

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions doc/services/portability/posix/option_groups/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,13 @@ process applications.
:widths: 50,10

confstr(),
environ,
environ,yes
errno,yes
getenv(),
setenv(),
getenv(),yes
setenv(),yes
sysconf(),yes
uname(),yes
unsetenv()
unsetenv(),yes

.. _posix_option_group_signals:

Expand Down
10 changes: 10 additions & 0 deletions lib/libc/minimal/include/stdlib.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ static inline long long llabs(long long __n)
return (__n < 0LL) ? -__n : __n;
}

char *getenv(const char *name);
#if _POSIX_C_SOURCE >= 200112L
int setenv(const char *name, const char *val, int overwrite);
int unsetenv(const char *name);
#endif

#ifdef _BSD_SOURCE
int getenv_r(const char *name, char *buf, size_t len);
#endif

#ifdef __cplusplus
}
#endif
Expand Down
1 change: 1 addition & 0 deletions lib/posix/options/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ zephyr_library_sources_ifdef(CONFIG_POSIX_API perror.c)
zephyr_library_sources_ifdef(CONFIG_POSIX_CLOCK clock.c)
zephyr_library_sources_ifdef(CONFIG_POSIX_CLOCK nanosleep.c)
zephyr_library_sources_ifdef(CONFIG_POSIX_CLOCK sleep.c)
zephyr_library_sources_ifdef(CONFIG_POSIX_ENV env.c)
zephyr_library_sources_ifdef(CONFIG_POSIX_FS fs.c)
zephyr_library_sources_ifdef(CONFIG_POSIX_MQUEUE mqueue.c)
zephyr_library_sources_ifdef(CONFIG_POSIX_PUTMSG stropts.c)
Expand Down
1 change: 1 addition & 0 deletions lib/posix/options/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ endif # POSIX_CLOCK
rsource "Kconfig.barrier"
rsource "Kconfig.clock"
rsource "Kconfig.cond"
rsource "Kconfig.env"
rsource "Kconfig.eventfd"
rsource "Kconfig.fdtable"
rsource "Kconfig.fnmatch"
Expand Down
14 changes: 14 additions & 0 deletions lib/posix/options/Kconfig.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright (c) 2023, Meta
#
# SPDX-License-Identifier: Apache-2.0

config POSIX_ENV
bool "Support for environ, getenv(), getenv_r(), setenv(), and unsetenv()"
depends on COMMON_LIBC_MALLOC
default y if POSIX_API
help
Select this option to add support for environment variables.

module = POSIX_ENV
module-str = POSIX env logging
source "subsys/logging/Kconfig.template.log_config"
267 changes: 267 additions & 0 deletions lib/posix/options/env.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
/*
* Copyright (c) 2023, Meta
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <zephyr/logging/log.h>
#include <zephyr/spinlock.h>

#define TRACK_ALLOC (IS_ENABLED(CONFIG_POSIX_ENV_LOG_LEVEL_DBG) || IS_ENABLED(CONFIG_ZTEST))

LOG_MODULE_REGISTER(posix_env, CONFIG_POSIX_ENV_LOG_LEVEL);

static struct k_spinlock environ_lock;
static size_t allocated;
char **environ;

#ifdef CONFIG_ZTEST
size_t posix_env_get_allocated_space(void)
{
return allocated;
}
#endif

static size_t environ_size(void)
{
size_t ret;

if (environ == NULL) {
return 0;
}

for (ret = 0; environ[ret] != NULL; ++ret) {
}

return ret;
}

static int findenv(const char *name, size_t namelen)
{
const char *env;

if (name == NULL || namelen == 0 || strchr(name, '=') != NULL) {
/* Note: '=' is not a valid name character */
return -EINVAL;
}

if (environ == NULL) {
return -ENOENT;
}

for (char **envp = &environ[0]; *envp != NULL; ++envp) {
env = *envp;
if (strncmp(env, name, namelen) == 0 && env[namelen] == '=') {
return envp - environ;
}
}

return -ENOENT;
}

char *getenv(const char *name)
{
int ret;
size_t nsize;
char *val = NULL;

nsize = (name == NULL) ? 0 : strlen(name);
K_SPINLOCK(&environ_lock)
{
ret = findenv(name, nsize);
if (ret < 0) {
K_SPINLOCK_BREAK;
}

val = environ[ret] + nsize + 1;
}

return val;
}

int getenv_r(const char *name, char *buf, size_t len)
{
int ret = 0;
size_t vsize;
size_t nsize;
char *val = NULL;

nsize = (name == NULL) ? 0 : strlen(name);
K_SPINLOCK(&environ_lock)
{
ret = findenv(name, nsize);
if (ret < 0) {
LOG_DBG("No entry for name '%s'", name);
K_SPINLOCK_BREAK;
}

val = environ[ret] + nsize + 1;
vsize = strlen(val) + 1;
if (vsize > len) {
ret = -ERANGE;
K_SPINLOCK_BREAK;
}
strncpy(buf, val, vsize);
LOG_DBG("Found entry %s", environ[ret]);
}

if (ret < 0) {
errno = -ret;
ret = -1;
}

return ret;
}

int setenv(const char *name, const char *val, int overwrite)
{
int ret = 0;
char *env;
char **envp;
size_t esize;
const size_t vsize = (val == NULL) ? 0 : strlen(val);
const size_t nsize = (name == NULL) ? 0 : strlen(name);
/* total size of name + '=' + val + '\0' */
const size_t tsize = nsize + 1 /* '=' */ + vsize + 1 /* '\0' */;

if (name == NULL || val == NULL) {
LOG_DBG("Invalid name '%s' or value '%s'", name, val);
errno = EINVAL;
return -1;
}

K_SPINLOCK(&environ_lock)
{
ret = findenv(name, nsize);
if (ret == -EINVAL) {
LOG_DBG("Invalid name '%s'", name);
K_SPINLOCK_BREAK;
}
if (ret >= 0) {
/* name was found in environ */
esize = strlen(environ[ret]) + 1;
if (overwrite == 0) {
LOG_DBG("Found entry %s", environ[ret]);
ret = 0;
K_SPINLOCK_BREAK;
}
} else {
/* name was not found in environ -> add new entry */
esize = environ_size();
envp = realloc(environ, sizeof(char **) *
(esize + 1 /* new entry */ + 1 /* NULL */));
if (envp == NULL) {
ret = -ENOMEM;
K_SPINLOCK_BREAK;
}

if (TRACK_ALLOC) {
allocated += sizeof(char **) * (esize + 2);
LOG_DBG("realloc %zu bytes (allocated: %zu)",
sizeof(char **) * (esize + 2), allocated);
}

environ = envp;
ret = esize;
environ[ret] = NULL;
environ[ret + 1] = NULL;
esize = 0;
}

if (esize < tsize) {
/* need to malloc or realloc space for new environ entry */
env = realloc(environ[ret], tsize);
if (env == NULL) {
ret = -ENOMEM;
K_SPINLOCK_BREAK;
}
if (TRACK_ALLOC) {
allocated += tsize - esize;
LOG_DBG("realloc %zu bytes (allocated: %zu)", tsize - esize,
allocated);
}
environ[ret] = env;
}

strncpy(environ[ret], name, nsize);
environ[ret][nsize] = '=';
strncpy(environ[ret] + nsize + 1, val, vsize + 1);
LOG_DBG("Added entry %s", environ[ret]);

ret = 0;
}

if (ret < 0) {
errno = -ret;
ret = -1;
}

return ret;
}

int unsetenv(const char *name)
{
int ret = 0;
char **envp;
size_t esize;
size_t nsize;

nsize = (name == NULL) ? 0 : strlen(name);
K_SPINLOCK(&environ_lock)
{
ret = findenv(name, nsize);
if (ret < 0) {
ret = (ret == -EINVAL) ? -EINVAL : 0;
K_SPINLOCK_BREAK;
}

esize = environ_size();
if (TRACK_ALLOC) {
allocated -= strlen(environ[ret]) + 1;
LOG_DBG("free %zu bytes (allocated: %zu)", strlen(environ[ret]) + 1,
allocated);
}
free(environ[ret]);

/* shuffle remaining environment variable pointers forward */
for (; ret < esize; ++ret) {
environ[ret] = environ[ret + 1];
}
/* environ must be terminated with a NULL pointer */
environ[ret] = NULL;

/* reduce environ size and update allocation */
--esize;
if (esize == 0) {
free(environ);
environ = NULL;
} else {
envp = realloc(environ, (esize + 1 /* NULL */) * sizeof(char **));
if (envp != NULL) {
environ = envp;
}
}
__ASSERT_NO_MSG((esize >= 1 && environ != NULL) || environ == NULL);

if (TRACK_ALLOC) {
/* recycle nsize here */
nsize = ((esize == 0) ? 2 : 1) * sizeof(char **);
allocated -= nsize;
LOG_DBG("free %zu bytes (allocated: %zu)", nsize, allocated);
}

ret = 0;
}

if (ret < 0) {
errno = -ret;
ret = -1;
}

return ret;
}
7 changes: 7 additions & 0 deletions lib/posix/shell/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Copyright (c) 2024 Meta
# SPDX-License-Identifier: Apache-2.0

zephyr_library()
# For setenv() and unsetenv()
zephyr_library_compile_options(-U_POSIX_C_SOURCE -D_POSIX_C_SOURCE=200809L)
# For getenv_r() visibility
zephyr_library_compile_definitions(_BSD_SOURCE)

zephyr_library_sources_ifdef(CONFIG_POSIX_SHELL posix_shell.c)
zephyr_library_sources_ifdef(CONFIG_POSIX_UNAME_SHELL uname.c)
zephyr_library_sources_ifdef(CONFIG_POSIX_ENV_SHELL env.c)
1 change: 1 addition & 0 deletions lib/posix/shell/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ config POSIX_SHELL
help
Compile the parent `posix` shell command.

rsource "Kconfig.env"
rsource "Kconfig.uname"

endif # SHELL
Expand Down
9 changes: 9 additions & 0 deletions lib/posix/shell/Kconfig.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2024 Meta
# SPDX-License-Identifier: Apache-2.0

config POSIX_ENV_SHELL
bool "Support for shell"
select POSIX_ENV
select POSIX_SHELL
help
This shell provides access to system environment variables.
Loading