diff --git a/Makefile.am b/Makefile.am index 1986b1a8..356dbf00 100644 --- a/Makefile.am +++ b/Makefile.am @@ -166,6 +166,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-eintr-handling.c \ test/test-embed.c \ test/test-emfile.c \ + test/test-env-vars.c \ test/test-error.c \ test/test-fail-always.c \ test/test-fs-event.c \ diff --git a/checksparse.sh b/checksparse.sh index 68e3bde3..5e022071 100755 --- a/checksparse.sh +++ b/checksparse.sh @@ -93,6 +93,7 @@ test/test-cwd-and-chdir.c test/test-delayed-accept.c test/test-dlerror.c test/test-embed.c +test/test-env-vars.c test/test-error.c test/test-fail-always.c test/test-fs-event.c diff --git a/docs/src/misc.rst b/docs/src/misc.rst index 71934694..5827787e 100644 --- a/docs/src/misc.rst +++ b/docs/src/misc.rst @@ -386,3 +386,38 @@ API stability guarantees. .. versionadded:: 1.8.0 + +.. c:function:: int uv_os_getenv(const char* name, char* buffer, size_t* size) + + Retrieves the environment variable specified by `name`, copies its value + into `buffer`, and sets `size` to the string length of the value. When + calling this function, `size` must be set to the amount of storage available + in `buffer`, including the null terminator. If the environment variable + exceeds the storage available in `buffer`, `UV_ENOBUFS` is returned, and + `size` is set to the amount of storage required to hold the value. If no + matching environment variable exists, `UV_ENOENT` is returned. + + .. warning:: + This function is not thread safe. + + .. versionadded:: 1.12.0 + +.. c:function:: int uv_os_setenv(const char* name, const char* value) + + Creates or updates the environment variable specified by `name` with + `value`. + + .. warning:: + This function is not thread safe. + + .. versionadded:: 1.12.0 + +.. c:function:: int uv_os_unsetenv(const char* name) + + Deletes the environment variable specified by `name`. If no such environment + variable exists, this function returns successfully. + + .. warning:: + This function is not thread safe. + + .. versionadded:: 1.12.0 diff --git a/include/uv.h b/include/uv.h index e5230bb5..9745bd29 100644 --- a/include/uv.h +++ b/include/uv.h @@ -1073,6 +1073,10 @@ UV_EXTERN int uv_interface_addresses(uv_interface_address_t** addresses, UV_EXTERN void uv_free_interface_addresses(uv_interface_address_t* addresses, int count); +UV_EXTERN int uv_os_getenv(const char* name, char* buffer, size_t* size); +UV_EXTERN int uv_os_setenv(const char* name, const char* value); +UV_EXTERN int uv_os_unsetenv(const char* name); + typedef enum { UV_FS_UNKNOWN = -1, diff --git a/src/unix/core.c b/src/unix/core.c index 9ef71349..2a7a6420 100644 --- a/src/unix/core.c +++ b/src/unix/core.c @@ -1240,3 +1240,48 @@ int uv_translate_sys_error(int sys_errno) { /* If < 0 then it's already a libuv error. */ return sys_errno <= 0 ? sys_errno : -sys_errno; } + + +int uv_os_getenv(const char* name, char* buffer, size_t* size) { + char* var; + size_t len; + + if (name == NULL || buffer == NULL || size == NULL || *size == 0) + return -EINVAL; + + var = getenv(name); + + if (var == NULL) + return -ENOENT; + + len = strlen(var); + + if (len >= *size) { + *size = len + 1; + return -ENOBUFS; + } + + memcpy(buffer, var, len + 1); + *size = len; + + return 0; +} + + +int uv_os_setenv(const char* name, const char* value) { + if (value == NULL) + return -EINVAL; + + if (setenv(name, value, 1) != 0) + return -errno; + + return 0; +} + + +int uv_os_unsetenv(const char* name) { + if (unsetenv(name) != 0) + return -errno; + + return 0; +} diff --git a/src/win/util.c b/src/win/util.c index 050058af..39f670c4 100644 --- a/src/win/util.c +++ b/src/win/util.c @@ -59,6 +59,9 @@ # define UNLEN 256 #endif +/* Maximum environment variable size, including the terminating null */ +#define MAX_ENV_VAR_LENGTH 32767 + /* Cached copy of the process title, plus a mutex guarding it. */ static char *process_title; static CRITICAL_SECTION process_title_lock; @@ -1387,3 +1390,180 @@ int uv__getpwuid_r(uv_passwd_t* pwd) { int uv_os_get_passwd(uv_passwd_t* pwd) { return uv__getpwuid_r(pwd); } + + +int uv_os_getenv(const char* name, char* buffer, size_t* size) { + wchar_t var[MAX_ENV_VAR_LENGTH]; + wchar_t* name_w; + DWORD bufsize; + size_t len; + int r; + + if (name == NULL || buffer == NULL || size == NULL || *size == 0) + return UV_EINVAL; + + /* Determine the size of the wide character name */ + bufsize = MultiByteToWideChar(CP_UTF8, 0, name, -1, NULL, 0); + + if (bufsize == 0) + return uv_translate_sys_error(GetLastError()); + + /* Convert the environment variable name to a wide character string */ + name_w = uv__malloc(sizeof(wchar_t) * bufsize); + + if (name_w == NULL) + return UV_ENOMEM; + + bufsize = MultiByteToWideChar(CP_UTF8, 0, name, -1, name_w, bufsize); + + if (bufsize == 0) { + uv__free(name_w); + return uv_translate_sys_error(GetLastError()); + } + + len = GetEnvironmentVariableW(name_w, var, MAX_ENV_VAR_LENGTH); + uv__free(name_w); + assert(len < MAX_ENV_VAR_LENGTH); /* len does not include the null */ + + if (len == 0) { + r = GetLastError(); + + if (r == ERROR_ENVVAR_NOT_FOUND) + return UV_ENOENT; + + return uv_translate_sys_error(r); + } + + /* Check how much space we need */ + bufsize = WideCharToMultiByte(CP_UTF8, 0, var, -1, NULL, 0, NULL, NULL); + + if (bufsize == 0) { + return uv_translate_sys_error(GetLastError()); + } else if (bufsize > *size) { + *size = bufsize; + return UV_ENOBUFS; + } + + /* Convert to UTF-8 */ + bufsize = WideCharToMultiByte(CP_UTF8, + 0, + var, + -1, + buffer, + *size, + NULL, + NULL); + + if (bufsize == 0) + return uv_translate_sys_error(GetLastError()); + + *size = bufsize - 1; + return 0; +} + + +int uv_os_setenv(const char* name, const char* value) { + wchar_t* name_w; + wchar_t* value_w; + DWORD bufsize; + int r; + + if (name == NULL || value == NULL) + return UV_EINVAL; + + name_w = NULL; + value_w = NULL; + r = 0; + + /* Determine the size of the wide character name */ + bufsize = MultiByteToWideChar(CP_UTF8, 0, name, -1, NULL, 0); + + if (bufsize == 0) { + r = uv_translate_sys_error(GetLastError()); + goto out; + } + + /* Convert the environment variable name to a wide character string */ + name_w = uv__malloc(sizeof(wchar_t) * bufsize); + + if (name_w == NULL) { + r = UV_ENOMEM; + goto out; + } + + bufsize = MultiByteToWideChar(CP_UTF8, 0, name, -1, name_w, bufsize); + + if (bufsize == 0) { + r = uv_translate_sys_error(GetLastError()); + goto out; + } + + /* Determine the size of the wide character value */ + bufsize = MultiByteToWideChar(CP_UTF8, 0, value, -1, NULL, 0); + + if (bufsize == 0) { + r = uv_translate_sys_error(GetLastError()); + goto out; + } + + /* Convert the environment variable value to a wide character string */ + value_w = uv__malloc(sizeof(wchar_t) * bufsize); + + if (value_w == NULL) { + r = UV_ENOMEM; + goto out; + } + + bufsize = MultiByteToWideChar(CP_UTF8, 0, value, -1, value_w, bufsize); + + if (bufsize == 0) { + r = uv_translate_sys_error(GetLastError()); + goto out; + } + + if (SetEnvironmentVariableW(name_w, value_w) == 0) + r = uv_translate_sys_error(GetLastError()); + +out: + uv__free(name_w); + uv__free(value_w); + + return r; +} + + +int uv_os_unsetenv(const char* name) { + wchar_t* name_w; + DWORD bufsize; + int r; + + if (name == NULL) + return UV_EINVAL; + + /* Determine the size of the wide character name */ + bufsize = MultiByteToWideChar(CP_UTF8, 0, name, -1, NULL, 0); + + if (bufsize == 0) + return uv_translate_sys_error(GetLastError()); + + /* Convert the environment variable name to a wide character string */ + name_w = uv__malloc(sizeof(wchar_t) * bufsize); + + if (name_w == NULL) + return UV_ENOMEM; + + bufsize = MultiByteToWideChar(CP_UTF8, 0, name, -1, name_w, bufsize); + + if (bufsize == 0) { + uv__free(name_w); + return uv_translate_sys_error(GetLastError()); + } + + r = SetEnvironmentVariableW(name_w, NULL); + uv__free(name_w); + + if (r == 0) + return uv_translate_sys_error(GetLastError()); + + return 0; +} diff --git a/test/test-env-vars.c b/test/test-env-vars.c new file mode 100644 index 00000000..7aa5881a --- /dev/null +++ b/test/test-env-vars.c @@ -0,0 +1,88 @@ +/* Copyright libuv contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "uv.h" +#include "task.h" +#include + +#define BUF_SIZE 10 + +TEST_IMPL(env_vars) { + const char* name = "UV_TEST_FOO"; + char buf[BUF_SIZE]; + size_t size; + int r; + + /* Reject invalid inputs when setting an environment variable */ + r = uv_os_setenv(NULL, "foo"); + ASSERT(r == UV_EINVAL); + r = uv_os_setenv(name, NULL); + ASSERT(r == UV_EINVAL); + + /* Reject invalid inputs when retrieving an environment variable */ + size = BUF_SIZE; + r = uv_os_getenv(NULL, buf, &size); + ASSERT(r == UV_EINVAL); + r = uv_os_getenv(name, NULL, &size); + ASSERT(r == UV_EINVAL); + r = uv_os_getenv(name, buf, NULL); + ASSERT(r == UV_EINVAL); + size = 0; + r = uv_os_getenv(name, buf, &size); + ASSERT(r == UV_EINVAL); + + /* Reject invalid inputs when deleting an environment variable */ + r = uv_os_unsetenv(NULL); + ASSERT(r == UV_EINVAL); + + /* Successfully set an environment variable */ + r = uv_os_setenv(name, "123456789"); + ASSERT(r == 0); + + /* Successfully read an environment variable */ + size = BUF_SIZE; + buf[0] = '\0'; + r = uv_os_getenv(name, buf, &size); + ASSERT(r == 0); + ASSERT(strcmp(buf, "123456789") == 0); + ASSERT(size == BUF_SIZE - 1); + + /* Return UV_ENOBUFS if the buffer cannot hold the environment variable */ + size = BUF_SIZE - 1; + buf[0] = '\0'; + r = uv_os_getenv(name, buf, &size); + ASSERT(r == UV_ENOBUFS); + ASSERT(size == BUF_SIZE); + + /* Successfully delete an environment variable */ + r = uv_os_unsetenv(name); + ASSERT(r == 0); + + /* Return UV_ENOENT retrieving an environment variable that does not exist */ + r = uv_os_getenv(name, buf, &size); + ASSERT(r == UV_ENOENT); + + /* Successfully delete an environment variable that does not exist */ + r = uv_os_unsetenv(name); + ASSERT(r == 0); + + return 0; +} diff --git a/test/test-list.h b/test/test-list.h index 35656a76..46da5fda 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -153,6 +153,7 @@ TEST_DECLARE (shutdown_close_pipe) TEST_DECLARE (shutdown_eof) TEST_DECLARE (shutdown_twice) TEST_DECLARE (callback_stack) +TEST_DECLARE (env_vars) TEST_DECLARE (error_message) TEST_DECLARE (sys_error) TEST_DECLARE (timer) @@ -550,6 +551,8 @@ TASK_LIST_START TEST_ENTRY (callback_stack) TEST_HELPER (callback_stack, tcp4_echo_server) + TEST_ENTRY (env_vars) + TEST_ENTRY (error_message) TEST_ENTRY (sys_error) diff --git a/uv.gyp b/uv.gyp index 9bbadd2e..e25ee25a 100644 --- a/uv.gyp +++ b/uv.gyp @@ -344,6 +344,7 @@ 'test/test-error.c', 'test/test-embed.c', 'test/test-emfile.c', + 'test/test-env-vars.c', 'test/test-fail-always.c', 'test/test-fs.c', 'test/test-fs-event.c',