diff --git a/tests/posix/env/CMakeLists.txt b/tests/posix/env/CMakeLists.txt new file mode 100644 index 00000000000..6cd1e565dcc --- /dev/null +++ b/tests/posix/env/CMakeLists.txt @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(posix_common) + +FILE(GLOB app_sources src/*.c) + +target_sources(app PRIVATE ${app_sources}) +# For setenv() and unsetenv() +target_compile_options(app PRIVATE -U_POSIX_C_SOURCE -D_POSIX_C_SOURCE=200809L) +# For getenv_r() visibility and testing +target_compile_definitions(app PRIVATE _BSD_SOURCE) diff --git a/tests/posix/env/prj.conf b/tests/posix/env/prj.conf new file mode 100644 index 00000000000..7389f7de33e --- /dev/null +++ b/tests/posix/env/prj.conf @@ -0,0 +1,3 @@ +CONFIG_ZTEST=y +CONFIG_POSIX_ENV=y +CONFIG_COMMON_LIBC_MALLOC=y diff --git a/tests/posix/env/src/env.c b/tests/posix/env/src/env.c new file mode 100644 index 00000000000..1d121a24438 --- /dev/null +++ b/tests/posix/env/src/env.c @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2023, Meta + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include + +#define M_HOME "/home/zephyr" +#define M_UID "1000" +#define M_PWD "/tmp" + +#define _m_alt_home "/this/path/is/much/longer/than" M_HOME + +#define DEFINE_ENVIRON(_handle, _key, _val) char _handle[] = _key "=" _val +#define RESET_ENVIRON(_handle, _key, _val) \ + snprintf(_handle, ARRAY_SIZE(_handle), "%s=%s", _key, _val) + +#if defined(CONFIG_NEWLIB_LIBC) || defined(CONFIG_PICOLIBC) +/* newlib headers seem to be missing this */ +int getenv_r(const char *name, char *val, size_t len); +#endif + +extern char **environ; +static char **old_environ; + +static DEFINE_ENVIRON(home, "HOME", M_HOME); +static DEFINE_ENVIRON(uid, "UID", M_UID); +static DEFINE_ENVIRON(pwd, "PWD", M_PWD); + +static char *environ_for_test[] = {home, uid, pwd, NULL}; + +ZTEST(env, test_getenv) +{ + zassert_equal(getenv(NULL), NULL); + zassert_equal(getenv(""), NULL); + zassert_equal(getenv("invalid=key"), NULL); + zassert_equal(getenv("HOME=" M_HOME), NULL); + zassert_equal(getenv("PWDR"), NULL); + + zassert_mem_equal(getenv("HOME"), M_HOME, strlen(M_HOME) + 1); + zassert_mem_equal(getenv("UID"), M_UID, strlen(M_UID) + 1); + zassert_mem_equal(getenv("PWD"), M_PWD, strlen(M_PWD) + 1); +} + +ZTEST(env, test_getenv_r) +{ + static char buf[16]; + static const int exp_errno[] = { + EINVAL, EINVAL, EINVAL, EINVAL, ENOENT, ENOENT, ENOENT, EINVAL, EINVAL, EINVAL, + }; + static const struct args_s { + const char *name; + char *buf; + size_t size; + } args[] = { + /* invalid input */ + {NULL, NULL, 0}, + {NULL, NULL, 42}, + {NULL, buf, 0}, + {NULL, buf, sizeof(buf)}, + {"hello", NULL, 0}, + {"hello", NULL, 42}, + {"hello", buf, 0}, + + /* invalid names */ + {"", buf, sizeof(buf)}, + {"invalid=key", buf, sizeof(buf)}, + {"HOME=", buf, sizeof(buf)}, + }; + + BUILD_ASSERT(ARRAY_SIZE(exp_errno) == ARRAY_SIZE(args)); + + ARRAY_FOR_EACH(args, i) + { + errno = 0; + zassert_equal(getenv_r(args[i].name, args[i].buf, args[i].size), -1, + "getenv_r(\"%s\", %p, %zu): expected to fail", args[i].name, + args[i].buf, args[i].size); + zassert_equal(errno, exp_errno[i], + "getenv_r(\"%s\", %p, %zu): act_errno: %d exp_errno: %d", + args[i].name, args[i].buf, args[i].size, errno, exp_errno[i]); + } + + zassert_mem_equal(getenv("HOME"), M_HOME, strlen(M_HOME) + 1); + zassert_mem_equal(getenv("UID"), M_UID, strlen(M_UID) + 1); + zassert_mem_equal(getenv("PWD"), M_PWD, strlen(M_PWD) + 1); +} + +ZTEST(env, test_setenv) +{ + zassert_equal(setenv(NULL, NULL, 0), -1); + zassert_equal(errno, EINVAL); + + /* + * bug in picolibc / newlib + * https://github.com/picolibc/picolibc/issues/648 + */ + zassert_equal(setenv("", "42", 0), -1); + zassert_equal(errno, EINVAL); + + zassert_equal(setenv("invalid=key", "42", 0), -1); + zassert_equal(errno, EINVAL); + + /* do not overwrite if environ[key] exists */ + zassert_ok(setenv("HOME", "/root", 0)); + zassert_mem_equal(getenv("HOME"), M_HOME, strlen(M_HOME) + 1); + + /* should overwrite (without malloc) */ + zassert_ok(setenv("HOME", "/root", 1)); + zassert_mem_equal(getenv("HOME"), "/root", strlen("/root") + 1); +} + +ZTEST(env, test_unsetenv) +{ + /* not hardened / application should fault */ + zassert_equal(unsetenv(NULL), -1); + zassert_equal(errno, EINVAL); + + errno = 0; + /* bug in picolibc / newlib */ + zassert_equal(unsetenv(""), -1); + zassert_equal(errno, EINVAL); + + zassert_equal(unsetenv("invalid=key"), -1); + zassert_equal(errno, EINVAL); + + /* restore original environ */ + environ = old_environ; + /* should overwrite (requires realloc) */ + zassert_ok(setenv("HOME", _m_alt_home, 1)); + zassert_mem_equal(getenv("HOME"), _m_alt_home, strlen(_m_alt_home) + 1); + zassert_ok(unsetenv("HOME")); + zassert_is_null(getenv("HOME")); +} + +ZTEST(env, test_watertight) +{ + extern size_t posix_env_get_allocated_space(void); + + char buf[4]; + + /* restore original environ, which should support realloc, free, etc */ + environ = old_environ; + + for (int i = 0; i < 256; ++i) { + snprintf(buf, sizeof(buf), "%u", i); + zassert_ok(setenv("COUNTER", buf, 1)); + zassert_mem_equal(getenv("COUNTER"), buf, strlen(buf)); + zassert_ok(getenv_r("COUNTER", buf, sizeof(buf))); + zassert_equal(atoi(buf), i); + zassert_ok(unsetenv("COUNTER")); + } + + zassert_equal(posix_env_get_allocated_space(), 0); +} + +static void before(void *arg) +{ + old_environ = environ; + + RESET_ENVIRON(home, "HOME", M_HOME); + RESET_ENVIRON(uid, "UID", M_UID); + RESET_ENVIRON(pwd, "PWD", M_PWD); + environ_for_test[0] = home; + environ_for_test[1] = uid; + environ_for_test[2] = pwd; + + zassert_equal((environ = environ_for_test), environ_for_test); +} + +static void after(void *arg) +{ + environ = old_environ; +} + +ZTEST_SUITE(env, NULL, NULL, before, after, NULL); diff --git a/tests/posix/env/testcase.yaml b/tests/posix/env/testcase.yaml new file mode 100644 index 00000000000..305fdfff5f8 --- /dev/null +++ b/tests/posix/env/testcase.yaml @@ -0,0 +1,32 @@ +common: + filter: not CONFIG_NATIVE_LIBC + arch_exclude: + - posix + integration_platforms: + - qemu_riscv64 + tags: posix +tests: + portability.posix.env: {} + portability.posix.env.armclang_std_libc: + toolchain_allow: armclang + extra_configs: + - CONFIG_ARMCLANG_STD_LIBC=y + portability.posix.env.arcmwdtlib: + toolchain_allow: arcmwdt + extra_configs: + - CONFIG_ARCMWDT_LIBC=y + portability.posix.env.minimal: + extra_configs: + - CONFIG_MINIMAL_LIBC=y + - CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=256 + portability.posix.env.newlib: + platform_exclude: + - hifive1 + filter: TOOLCHAIN_HAS_NEWLIB == 1 + extra_configs: + - CONFIG_NEWLIB_LIBC=y + portability.posix.env.picolibc: + tags: picolibc + filter: CONFIG_PICOLIBC_SUPPORTED + extra_configs: + - CONFIG_PICOLIBC=y