/* * Copyright (c) 2023, Meta * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #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; } strcpy(buf, val); 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; } strcpy(environ[ret], name); 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; }