diff --git a/include/stdlib.h b/include/stdlib.h --- a/include/stdlib.h +++ b/include/stdlib.h @@ -95,6 +95,9 @@ _Noreturn void exit(int); void free(void *); char *getenv(const char *); +#if __BSD_VISIBLE +int getenv_r(const char *, char * _Nonnull, size_t); +#endif long labs(long) __pure2; ldiv_t ldiv(long, long) __pure2; void *malloc(size_t) __malloc_like __result_use_check __alloc_size(1); diff --git a/lib/libc/stdlib/Symbol.map b/lib/libc/stdlib/Symbol.map --- a/lib/libc/stdlib/Symbol.map +++ b/lib/libc/stdlib/Symbol.map @@ -127,6 +127,10 @@ secure_getenv; }; +FBSD_1.8 { + getenv_r; +}; + FBSDprivate_1.0 { __system; _system; diff --git a/lib/libc/stdlib/getenv.3 b/lib/libc/stdlib/getenv.3 --- a/lib/libc/stdlib/getenv.3 +++ b/lib/libc/stdlib/getenv.3 @@ -29,12 +29,13 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd April 17, 2025 +.Dd April 22, 2025 .Dt GETENV 3 .Os .Sh NAME .Nm clearenv , .Nm getenv , +.Nm getenv_r , .Nm putenv , .Nm secure_getenv , .Nm setenv , @@ -48,6 +49,8 @@ .Fn clearenv "void" .Ft char * .Fn getenv "const char *name" +.Ft int +.Fn getenv_r "const char *name" "char *buf" "size_t len" .Ft char * .Fn secure_getenv "const char *name" .Ft int @@ -71,7 +74,8 @@ .Pp The .Fn getenv -function obtains the current value of the environment variable, +function obtains the current value of the environment variable +designated by .Fa name . The application should not modify the string pointed to by the @@ -79,6 +83,16 @@ function. .Pp The +.Fn getenv_r +function obtains the current value of the environment variable +designated by +.Fa name +and copies it into the buffer +.Fa buf +of length +.Fa len . +.Pp +The .Fn secure_getenv returns .Va NULL @@ -157,12 +171,13 @@ if the process is in "secure execution," otherwise it will call .Fn getenv . .Pp -.Rv -std clearenv setenv putenv unsetenv +.Rv -std clearenv getenv_r setenv putenv unsetenv .Sh ERRORS .Bl -tag -width Er .It Bq Er EINVAL The function .Fn getenv , +.Fn getenv_r , .Fn setenv or .Fn unsetenv @@ -191,6 +206,11 @@ This does not follow the .Tn POSIX specification. +.It Bq Er ENOENT +The function +.Fn getenv_r +failed because the requested variable was not found in the +environment. .It Bq Er ENOMEM The function .Fn setenv , @@ -198,6 +218,11 @@ or .Fn putenv failed because they were unable to allocate memory for the environment. +.It Bq Er ERANGE +The function +.Fn getenv_r +failed because the value of the requested variable was too long to fit +in the provided buffer. .El .Sh SEE ALSO .Xr csh 1 , @@ -251,6 +276,13 @@ .Fn secure_getenv functions were added in .Fx 14 . +.Pp +The +.Fn getenv_r +function first appeared in +.Nx 4.0 +and was added in +.Fx 15 . .Sh BUGS Successive calls to .Fn setenv diff --git a/lib/libc/stdlib/getenv.c b/lib/libc/stdlib/getenv.c --- a/lib/libc/stdlib/getenv.c +++ b/lib/libc/stdlib/getenv.c @@ -443,6 +443,41 @@ } +/* + * Like getenv(), but copies the value into the provided buffer. + */ +int +getenv_r(const char *name, char *buf, size_t len) +{ + const char *val; + size_t nameLen; + int envNdx; + + if (name == NULL || (nameLen = __strleneq(name)) == 0) { + errno = EINVAL; + return (-1); + } + + if (environ == NULL || environ[0] == NULL) { + val = NULL; + } else if (envVars == NULL || environ != intEnviron) { + val = __findenv_environ(name, nameLen); + } else { + envNdx = envVarsTotal - 1; + val = __findenv(name, nameLen, &envNdx, true); + } + if (val == NULL) { + errno = ENOENT; + return (-1); + } + if (strlcpy(buf, val, len) >= len) { + errno = ERANGE; + return (-1); + } + return (0); +} + + /* * Runs getenv() unless the current process is tainted by uid or gid changes, in * which case it will return NULL. diff --git a/lib/libc/tests/stdlib/Makefile b/lib/libc/tests/stdlib/Makefile --- a/lib/libc/tests/stdlib/Makefile +++ b/lib/libc/tests/stdlib/Makefile @@ -3,6 +3,7 @@ ATF_TESTS_C+= clearenv_test ATF_TESTS_C+= cxa_atexit_test ATF_TESTS_C+= dynthr_test +ATF_TESTS_C+= getenv_r_test ATF_TESTS_C+= heapsort_test ATF_TESTS_C+= libc_exit_test ATF_TESTS_C+= mergesort_test diff --git a/lib/libc/tests/stdlib/getenv_r_test.c b/lib/libc/tests/stdlib/getenv_r_test.c new file mode 100644 --- /dev/null +++ b/lib/libc/tests/stdlib/getenv_r_test.c @@ -0,0 +1,69 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +#include + +ATF_TC_WITHOUT_HEAD(getenv_r_ok); +ATF_TC_BODY(getenv_r_ok, tc) +{ + const char *ident = atf_tc_get_ident(tc); + char buf[256]; + + ATF_REQUIRE_EQ(0, setenv("ATF_TC_IDENT", ident, 1)); + ATF_REQUIRE_EQ(0, getenv_r("ATF_TC_IDENT", buf, sizeof(buf))); + ATF_REQUIRE_STREQ(ident, buf); +} + +ATF_TC_WITHOUT_HEAD(getenv_r_einval); +ATF_TC_BODY(getenv_r_einval, tc) +{ + char buf[256]; + + errno = 0; + ATF_REQUIRE_EQ(-1, getenv_r(NULL, buf, sizeof(buf))); + ATF_REQUIRE_EQ(EINVAL, errno); + errno = 0; + ATF_REQUIRE_EQ(-1, getenv_r("", buf, sizeof(buf))); + ATF_REQUIRE_EQ(EINVAL, errno); + errno = 0; + ATF_REQUIRE_EQ(-1, getenv_r("A=B", buf, sizeof(buf))); + ATF_REQUIRE_EQ(EINVAL, errno); +} + +ATF_TC_WITHOUT_HEAD(getenv_r_enoent); +ATF_TC_BODY(getenv_r_enoent, tc) +{ + char buf[256]; + + errno = 0; + ATF_REQUIRE_EQ(-1, getenv_r("no such variable", buf, sizeof(buf))); + ATF_REQUIRE_EQ(ENOENT, errno); +} + +ATF_TC_WITHOUT_HEAD(getenv_r_erange); +ATF_TC_BODY(getenv_r_erange, tc) +{ + const char *ident = atf_tc_get_ident(tc); + char buf[256]; + + ATF_REQUIRE_EQ(0, setenv("ATF_TC_IDENT", ident, 1)); + errno = 0; + ATF_REQUIRE_EQ(-1, getenv_r(NULL, buf, strlen(ident))); + ATF_REQUIRE_EQ(ERANGE, errno); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, getenv_r_ok); + ATF_TP_ADD_TC(tp, getenv_r_einval); + ATF_TP_ADD_TC(tp, getenv_r_enoent); + ATF_TP_ADD_TC(tp, getenv_r_erange); + return (atf_no_error()); +}