Merge #7920 'env: use libuv v1.12 getenv/setenv API'

This commit is contained in:
Justin M. Keyes
2019-03-01 02:30:05 +01:00
committed by GitHub
21 changed files with 370 additions and 118 deletions

View File

@@ -379,7 +379,7 @@ endif()
include_directories("${PROJECT_BINARY_DIR}/config") include_directories("${PROJECT_BINARY_DIR}/config")
include_directories("${PROJECT_SOURCE_DIR}/src") include_directories("${PROJECT_SOURCE_DIR}/src")
find_package(LibUV REQUIRED) find_package(LibUV REQUIRED) # minimum version: v1.12
include_directories(SYSTEM ${LIBUV_INCLUDE_DIRS}) include_directories(SYSTEM ${LIBUV_INCLUDE_DIRS})
find_package(Msgpack 1.0.0 REQUIRED) find_package(Msgpack 1.0.0 REQUIRED)

View File

@@ -47,17 +47,8 @@ if(Iconv_FOUND)
set(HAVE_ICONV 1) set(HAVE_ICONV 1)
endif() endif()
check_function_exists(_putenv_s HAVE_PUTENV_S)
if(WIN32 AND NOT HAVE_PUTENV_S)
message(SEND_ERROR "_putenv_s() function not found on your system.")
endif()
check_function_exists(opendir HAVE_OPENDIR) check_function_exists(opendir HAVE_OPENDIR)
check_function_exists(readlink HAVE_READLINK) check_function_exists(readlink HAVE_READLINK)
check_function_exists(setenv HAVE_SETENV)
if(UNIX AND NOT HAVE_SETENV)
message(SEND_ERROR "setenv() function not found on your system.")
endif()
check_function_exists(unsetenv HAVE_UNSETENV)
check_function_exists(setpgid HAVE_SETPGID) check_function_exists(setpgid HAVE_SETPGID)
check_function_exists(setsid HAVE_SETSID) check_function_exists(setsid HAVE_SETSID)
check_function_exists(sigaction HAVE_SIGACTION) check_function_exists(sigaction HAVE_SIGACTION)

View File

@@ -27,14 +27,11 @@
#cmakedefine HAVE_LOCALE_H #cmakedefine HAVE_LOCALE_H
#cmakedefine HAVE_NL_LANGINFO_CODESET #cmakedefine HAVE_NL_LANGINFO_CODESET
#cmakedefine HAVE_NL_MSG_CAT_CNTR #cmakedefine HAVE_NL_MSG_CAT_CNTR
#cmakedefine HAVE_PUTENV_S
#cmakedefine HAVE_PWD_H #cmakedefine HAVE_PWD_H
#cmakedefine HAVE_READLINK #cmakedefine HAVE_READLINK
#cmakedefine HAVE_UV_TRANSLATE_SYS_ERROR #cmakedefine HAVE_UV_TRANSLATE_SYS_ERROR
// TODO: add proper cmake check // TODO: add proper cmake check
// #define HAVE_SELINUX 1 // #define HAVE_SELINUX 1
#cmakedefine HAVE_SETENV
#cmakedefine HAVE_UNSETENV
#cmakedefine HAVE_SETPGID #cmakedefine HAVE_SETPGID
#cmakedefine HAVE_SETSID #cmakedefine HAVE_SETSID
#cmakedefine HAVE_SIGACTION #cmakedefine HAVE_SIGACTION

View File

@@ -29,10 +29,10 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Does neovim-lint on c files. """Lints C files in the Neovim source tree.
The goal of this script is to identify places in the code that *may* The goal of this script is to identify places in the code that *may*
be in non-compliance with neovim style. It does not attempt to fix be in non-compliance with Neovim style. It does not attempt to fix
up these problems -- the point is to educate. It does also not up these problems -- the point is to educate. It does also not
attempt to find all problems, or to ensure that everything it does attempt to find all problems, or to ensure that everything it does
find is legitimately a problem. find is legitimately a problem.
@@ -88,7 +88,7 @@ Syntax: clint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...]
* [whitespace/braces] { should almost always be at the end of the previous * [whitespace/braces] { should almost always be at the end of the previous
line line
* [build/include] Include the directory when naming .h files * [build/include] Include the directory when naming .h files
* [runtime/int] Use int16/int64/etc, rather than the C type. * [runtime/int] Use int16_t/int64_t/etc, rather than the C type.
Every problem is given a confidence score from 1-5, with 5 meaning we are Every problem is given a confidence score from 1-5, with 5 meaning we are
certain of the problem, and 1 meaning it could be a legitimate construct. certain of the problem, and 1 meaning it could be a legitimate construct.
@@ -1487,6 +1487,37 @@ def CheckMemoryFunctions(filename, clean_lines, linenum, error):
'...) instead of ' + function + '...).') '...) instead of ' + function + '...).')
os_functions = (
('setenv(', 'os_setenv('),
('getenv(', 'os_getenv('),
('_wputenv(', 'os_setenv('),
('_putenv_s(', 'os_setenv('),
('putenv(', 'os_setenv('),
('unsetenv(', 'os_unsetenv('),
)
def CheckOSFunctions(filename, clean_lines, linenum, error):
"""Checks for calls to invalid functions.
Args:
filename: The name of the current file.
clean_lines: A CleansedLines instance containing the file.
linenum: The number of the line to check.
error: The function to call with any errors found.
"""
line = clean_lines.elided[linenum]
for function, suggested_function in os_functions:
ix = line.find(function)
# Comparisons made explicit for clarity -- pylint:
# disable=g-explicit-bool-comparison
if ix >= 0 and (ix == 0 or (not line[ix - 1].isalnum() and
line[ix - 1] not in ('_', '.', '>'))):
error(filename, linenum, 'runtime/os_fn', 2,
'Use ' + suggested_function +
'...) instead of ' + function + '...).')
# Matches invalid increment: *count++, which moves pointer instead of # Matches invalid increment: *count++, which moves pointer instead of
# incrementing a value. # incrementing a value.
_RE_PATTERN_INVALID_INCREMENT = re.compile( _RE_PATTERN_INVALID_INCREMENT = re.compile(
@@ -3370,6 +3401,7 @@ def ProcessLine(filename, file_extension, clean_lines, line,
nesting_state, error) nesting_state, error)
CheckPosixThreading(filename, clean_lines, line, error) CheckPosixThreading(filename, clean_lines, line, error)
CheckMemoryFunctions(filename, clean_lines, line, error) CheckMemoryFunctions(filename, clean_lines, line, error)
CheckOSFunctions(filename, clean_lines, line, error)
for check_fn in extra_check_functions: for check_fn in extra_check_functions:
check_fn(filename, clean_lines, line, error) check_fn(filename, clean_lines, line, error)

View File

@@ -19,7 +19,7 @@ typedef size_t hash_T;
#define HASHITEM_EMPTY(hi) ((hi)->hi_key == NULL \ #define HASHITEM_EMPTY(hi) ((hi)->hi_key == NULL \
|| (hi)->hi_key == (char_u *)&hash_removed) || (hi)->hi_key == (char_u *)&hash_removed)
/// A hastable item. /// Hashtable item.
/// ///
/// Each item has a NUL terminated string key. /// Each item has a NUL terminated string key.
/// A key can appear only once in the table. /// A key can appear only once in the table.

View File

@@ -24,7 +24,7 @@
*/ */
/* /*
An example: Example:
#include "nvim/khash.h" #include "nvim/khash.h"
KHASH_MAP_INIT_INT(32, char) KHASH_MAP_INIT_INT(32, char)

View File

@@ -184,6 +184,7 @@ bool event_teardown(void)
void early_init(void) void early_init(void)
{ {
log_init(); log_init();
env_init();
fs_init(); fs_init();
handle_init(); handle_init();
eval_init(); // init global variables eval_init(); // init global variables
@@ -1769,7 +1770,7 @@ static bool do_user_initialization(void)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_WARN_UNUSED_RESULT
{ {
bool do_exrc = p_exrc; bool do_exrc = p_exrc;
if (process_env("VIMINIT") == OK) { if (execute_env("VIMINIT") == OK) {
do_exrc = p_exrc; do_exrc = p_exrc;
return do_exrc; return do_exrc;
} }
@@ -1814,7 +1815,7 @@ static bool do_user_initialization(void)
} while (iter != NULL); } while (iter != NULL);
xfree(config_dirs); xfree(config_dirs);
} }
if (process_env("EXINIT") == OK) { if (execute_env("EXINIT") == OK) {
do_exrc = p_exrc; do_exrc = p_exrc;
return do_exrc; return do_exrc;
} }
@@ -1878,7 +1879,7 @@ static void source_startup_scripts(const mparm_T *const parmp)
/// ///
/// @return FAIL if the environment variable was not executed, /// @return FAIL if the environment variable was not executed,
/// OK otherwise. /// OK otherwise.
static int process_env(char *env) static int execute_env(char *env)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_ALL
{ {
const char *initstr = os_getenv(env); const char *initstr = os_getenv(env);

View File

@@ -1,6 +1,13 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check // This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
///
/// map.c: khash.h wrapper
///
/// NOTE: Callers must manage memory (allocate) for keys and values.
/// khash.h does not make its own copy of the key or value.
///
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
@@ -72,6 +79,16 @@
return kh_get(T##_##U##_map, map->table, key) != kh_end(map->table); \ return kh_get(T##_##U##_map, map->table, key) != kh_end(map->table); \
} \ } \
\ \
T map_##T##_##U##_key(Map(T, U) *map, T key) \
{ \
khiter_t k; \
\
if ((k = kh_get(T##_##U##_map, map->table, key)) == kh_end(map->table)) { \
abort(); /* Caller must check map_has(). */ \
} \
\
return kh_key(map->table, k); \
} \
U map_##T##_##U##_put(Map(T, U) *map, T key, U value) \ U map_##T##_##U##_put(Map(T, U) *map, T key, U value) \
{ \ { \
int ret; \ int ret; \
@@ -167,3 +184,18 @@ MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER)
#define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL } #define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL }
MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER) MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER)
MAP_IMPL(String, handle_T, 0) MAP_IMPL(String, handle_T, 0)
/// Deletes a key:value pair from a string:pointer map, and frees the
/// storage of both key and value.
///
void pmap_del2(PMap(cstr_t) *map, const char *key)
{
if (pmap_has(cstr_t)(map, key)) {
void *k = (void *)pmap_key(cstr_t)(map, key);
void *v = pmap_get(cstr_t)(map, key);
pmap_del(cstr_t)(map, key);
xfree(k);
xfree(v);
}
}

View File

@@ -25,11 +25,15 @@
void map_##T##_##U##_free(Map(T, U) *map); \ void map_##T##_##U##_free(Map(T, U) *map); \
U map_##T##_##U##_get(Map(T, U) *map, T key); \ U map_##T##_##U##_get(Map(T, U) *map, T key); \
bool map_##T##_##U##_has(Map(T, U) *map, T key); \ bool map_##T##_##U##_has(Map(T, U) *map, T key); \
T map_##T##_##U##_key(Map(T, U) *map, T key); \
U map_##T##_##U##_put(Map(T, U) *map, T key, U value); \ U map_##T##_##U##_put(Map(T, U) *map, T key, U value); \
U *map_##T##_##U##_ref(Map(T, U) *map, T key, bool put); \ U *map_##T##_##U##_ref(Map(T, U) *map, T key, bool put); \
U map_##T##_##U##_del(Map(T, U) *map, T key); \ U map_##T##_##U##_del(Map(T, U) *map, T key); \
void map_##T##_##U##_clear(Map(T, U) *map); void map_##T##_##U##_clear(Map(T, U) *map);
//
// NOTE: Keys AND values must be allocated! khash.h does not make a copy.
//
MAP_DECLS(int, int) MAP_DECLS(int, int)
MAP_DECLS(cstr_t, ptr_t) MAP_DECLS(cstr_t, ptr_t)
MAP_DECLS(ptr_t, ptr_t) MAP_DECLS(ptr_t, ptr_t)
@@ -43,6 +47,7 @@ MAP_DECLS(String, handle_T)
#define map_free(T, U) map_##T##_##U##_free #define map_free(T, U) map_##T##_##U##_free
#define map_get(T, U) map_##T##_##U##_get #define map_get(T, U) map_##T##_##U##_get
#define map_has(T, U) map_##T##_##U##_has #define map_has(T, U) map_##T##_##U##_has
#define map_key(T, U) map_##T##_##U##_key
#define map_put(T, U) map_##T##_##U##_put #define map_put(T, U) map_##T##_##U##_put
#define map_ref(T, U) map_##T##_##U##_ref #define map_ref(T, U) map_##T##_##U##_ref
#define map_del(T, U) map_##T##_##U##_del #define map_del(T, U) map_##T##_##U##_del
@@ -52,7 +57,9 @@ MAP_DECLS(String, handle_T)
#define pmap_free(T) map_free(T, ptr_t) #define pmap_free(T) map_free(T, ptr_t)
#define pmap_get(T) map_get(T, ptr_t) #define pmap_get(T) map_get(T, ptr_t)
#define pmap_has(T) map_has(T, ptr_t) #define pmap_has(T) map_has(T, ptr_t)
#define pmap_key(T) map_key(T, ptr_t)
#define pmap_put(T) map_put(T, ptr_t) #define pmap_put(T) map_put(T, ptr_t)
/// @see pmap_del2
#define pmap_del(T) map_del(T, ptr_t) #define pmap_del(T) map_del(T, ptr_t)
#define pmap_clear(T) map_clear(T, ptr_t) #define pmap_clear(T) map_clear(T, ptr_t)
@@ -62,4 +69,6 @@ MAP_DECLS(String, handle_T)
#define map_foreach_value(map, value, block) \ #define map_foreach_value(map, value, block) \
kh_foreach_value(map->table, value, block) kh_foreach_value(map->table, value, block)
void pmap_del2(PMap(cstr_t) *map, const char *key);
#endif // NVIM_MAP_H #endif // NVIM_MAP_H

View File

@@ -1375,6 +1375,7 @@ int utf8_to_utf16(const char *str, wchar_t **strw)
int utf16_to_utf8(const wchar_t *strw, char **str) int utf16_to_utf8(const wchar_t *strw, char **str)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_ALL
{ {
*str = NULL;
// Compute the space required to store the string as UTF-8. // Compute the space required to store the string as UTF-8.
DWORD utf8_len = WideCharToMultiByte(CP_UTF8, DWORD utf8_len = WideCharToMultiByte(CP_UTF8,
0, 0,
@@ -1400,7 +1401,7 @@ int utf16_to_utf8(const wchar_t *strw, char **str)
NULL, NULL,
NULL); NULL);
if (utf8_len == 0) { if (utf8_len == 0) {
free(*str); xfree(*str);
*str = NULL; *str = NULL;
return GetLastError(); return GetLastError();
} }

View File

@@ -109,7 +109,7 @@ void *xmalloc(size_t size)
return ret; return ret;
} }
/// free wrapper that returns delegates to the backing memory manager /// free() wrapper that delegates to the backing memory manager
void xfree(void *ptr) void xfree(void *ptr)
{ {
free(ptr); free(ptr);

View File

@@ -19,6 +19,7 @@
#include "nvim/eval.h" #include "nvim/eval.h"
#include "nvim/ex_getln.h" #include "nvim/ex_getln.h"
#include "nvim/version.h" #include "nvim/version.h"
#include "nvim/map.h"
#ifdef WIN32 #ifdef WIN32
#include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8 #include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8
@@ -32,65 +33,119 @@
#include <sys/utsname.h> #include <sys/utsname.h>
#endif #endif
// Because `uv_os_getenv` requires allocating, we must manage a map to maintain
// the behavior of `os_getenv`.
static PMap(cstr_t) *envmap;
static uv_mutex_t mutex;
void env_init(void)
{
envmap = pmap_new(cstr_t)();
uv_mutex_init(&mutex);
}
/// Like getenv(), but returns NULL if the variable is empty. /// Like getenv(), but returns NULL if the variable is empty.
const char *os_getenv(const char *name) const char *os_getenv(const char *name)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_ALL
{ {
const char *e = getenv(name); char *e;
return e == NULL || *e == NUL ? NULL : e; size_t size = 64;
if (name[0] == '\0') {
return NULL;
}
uv_mutex_lock(&mutex);
if (pmap_has(cstr_t)(envmap, name)
&& !!(e = (char *)pmap_get(cstr_t)(envmap, name))) {
if (e[0] != '\0') {
// Found non-empty cached env var.
// NOTE: This risks incoherence if an in-process library changes the
// environment without going through our os_setenv() wrapper. If
// that turns out to be a problem, we can just remove this codepath.
goto end;
}
pmap_del2(envmap, name);
}
e = xmalloc(size);
int r = uv_os_getenv(name, e, &size);
if (r == UV_ENOBUFS) {
e = xrealloc(e, size);
r = uv_os_getenv(name, e, &size);
}
if (r != 0 || size == 0 || e[0] == '\0') {
xfree(e);
e = NULL;
if (r != 0 && r != UV_ENOENT && r != UV_UNKNOWN) {
ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r));
}
goto end;
}
pmap_put(cstr_t)(envmap, xstrdup(name), e);
end:
uv_mutex_unlock(&mutex);
return (e == NULL || size == 0 || e[0] == '\0') ? NULL : e;
} }
/// Returns `true` if the environment variable, `name`, has been defined /// Returns true if environment variable `name` is defined (even if empty).
/// (even if empty). /// Returns false if not found (UV_ENOENT) or other failure.
bool os_env_exists(const char *name) bool os_env_exists(const char *name)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_ALL
{ {
return getenv(name) != NULL; if (name[0] == '\0') {
return false;
}
// Use a tiny buffer because we don't care about the value: if uv_os_getenv()
// returns UV_ENOBUFS, the env var was found.
char buf[1];
size_t size = sizeof(buf);
int r = uv_os_getenv(name, buf, &size);
assert(r != UV_EINVAL);
if (r != 0 && r != UV_ENOENT && r != UV_ENOBUFS) {
ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r));
}
return (r == 0 || r == UV_ENOBUFS);
} }
int os_setenv(const char *name, const char *value, int overwrite) int os_setenv(const char *name, const char *value, int overwrite)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_ALL
{ {
#ifdef WIN32 if (name[0] == '\0') {
size_t envbuflen = strlen(name) + strlen(value) + 2;
char *envbuf = xmalloc(envbuflen);
snprintf(envbuf, envbuflen, "%s=%s", name, value);
wchar_t *p;
utf8_to_utf16(envbuf, &p);
xfree(envbuf);
if (p == NULL) {
return -1; return -1;
} }
_wputenv(p); #ifdef WIN32
xfree(p); // Unlike Unix systems, we can free the string for _wputenv().
return 0;
#elif defined(HAVE_SETENV)
return setenv(name, value, overwrite);
#elif defined(HAVE_PUTENV_S)
if (!overwrite && os_getenv(name) != NULL) { if (!overwrite && os_getenv(name) != NULL) {
return 0; return 0;
} }
if (_putenv_s(name, value) == 0) { #else
if (!overwrite && os_env_exists(name)) {
return 0; return 0;
} }
return -1;
#else
# error "This system has no implementation available for os_setenv()"
#endif #endif
uv_mutex_lock(&mutex);
pmap_del2(envmap, name);
int r = uv_os_setenv(name, value);
assert(r != UV_EINVAL);
if (r != 0) {
ELOG("uv_os_setenv(%s) failed: %d %s", name, r, uv_err_name(r));
}
uv_mutex_unlock(&mutex);
return r == 0 ? 0 : -1;
} }
/// Unset environment variable /// Unset environment variable
///
/// For systems where unsetenv() is not available the value will be set as an
/// empty string
int os_unsetenv(const char *name) int os_unsetenv(const char *name)
FUNC_ATTR_NONNULL_ALL
{ {
#ifdef HAVE_UNSETENV if (name[0] == '\0') {
return unsetenv(name); return -1;
#else }
return os_setenv(name, "", 1); uv_mutex_lock(&mutex);
#endif pmap_del2(envmap, name);
int r = uv_os_unsetenv(name);
if (r != 0) {
ELOG("uv_os_unsetenv(%s) failed: %d %s", name, r, uv_err_name(r));
}
uv_mutex_unlock(&mutex);
return r == 0 ? 0 : -1;
} }
char *os_getenvname_at_index(size_t index) char *os_getenvname_at_index(size_t index)
@@ -527,7 +582,7 @@ static char *remove_tail(char *path, char *pend, char *dirname)
return pend; return pend;
} }
/// Iterate over a delimited list. /// Iterates $PATH-like delimited list `val`.
/// ///
/// @note Environment variables must not be modified during iteration. /// @note Environment variables must not be modified during iteration.
/// ///
@@ -562,7 +617,7 @@ const void *vim_env_iter(const char delim,
} }
} }
/// Iterate over a delimited list in reverse order. /// Iterates $PATH-like delimited list `val` in reverse order.
/// ///
/// @note Environment variables must not be modified during iteration. /// @note Environment variables must not be modified during iteration.
/// ///
@@ -599,11 +654,12 @@ const void *vim_env_iter_rev(const char delim,
} }
} }
/// Vim's version of getenv(). /// Vim getenv() wrapper with special handling of $HOME, $VIM, $VIMRUNTIME,
/// Special handling of $HOME, $VIM and $VIMRUNTIME, allowing the user to /// allowing the user to override the Nvim runtime directory at runtime.
/// override the vim runtime directory at runtime. Also does ACP to 'enc' /// Result must be freed by the caller.
/// conversion for Win32. Result must be freed by the caller. ///
/// @param name Environment variable to expand /// @param name Environment variable to expand
/// @return [allocated] Expanded environment variable, or NULL
char *vim_getenv(const char *name) char *vim_getenv(const char *name)
{ {
// init_path() should have been called before now. // init_path() should have been called before now.
@@ -869,9 +925,8 @@ char_u * home_replace_save(buf_T *buf, char_u *src) FUNC_ATTR_NONNULL_RET
return dst; return dst;
} }
/// Our portable version of setenv. /// Vim setenv() wrapper with special handling for $VIMRUNTIME to keep the
/// Has special handling for $VIMRUNTIME to keep the localization machinery /// localization machinery sane.
/// sane.
void vim_setenv(const char *name, const char *val) void vim_setenv(const char *name, const char *val)
{ {
os_setenv(name, val, 1); os_setenv(name, val, 1);

View File

@@ -21,9 +21,7 @@ typedef struct {
uv_dirent_t ent; ///< @private The entry information. uv_dirent_t ent; ///< @private The entry information.
} Directory; } Directory;
/// Function to convert libuv error to char * error description /// Converts libuv error (negative int) to error description string.
///
/// negative libuv error codes are returned by a number of os functions.
#define os_strerror uv_strerror #define os_strerror uv_strerror
// Values returned by os_nodetype() // Values returned by os_nodetype()

View File

@@ -157,11 +157,11 @@ static void init_child(PtyProcess *ptyproc)
// New session/process-group. #6530 // New session/process-group. #6530
setsid(); setsid();
unsetenv("COLUMNS"); os_unsetenv("COLUMNS");
unsetenv("LINES"); os_unsetenv("LINES");
unsetenv("TERMCAP"); os_unsetenv("TERMCAP");
unsetenv("COLORTERM"); os_unsetenv("COLORTERM");
unsetenv("COLORFGBG"); os_unsetenv("COLORFGBG");
signal(SIGCHLD, SIG_DFL); signal(SIGCHLD, SIG_DFL);
signal(SIGHUP, SIG_DFL); signal(SIGHUP, SIG_DFL);
@@ -177,7 +177,7 @@ static void init_child(PtyProcess *ptyproc)
} }
char *prog = ptyproc->process.argv[0]; char *prog = ptyproc->process.argv[0];
setenv("TERM", ptyproc->term_name ? ptyproc->term_name : "ansi", 1); os_setenv("TERM", ptyproc->term_name ? ptyproc->term_name : "ansi", 1);
execvp(prog, ptyproc->process.argv); execvp(prog, ptyproc->process.argv);
ELOG("execvp failed: %s: %s", strerror(errno), prog); ELOG("execvp failed: %s: %s", strerror(errno), prog);
_exit(122); // 122 is EXEC_FAILED in the Vim source. _exit(122); // 122 is EXEC_FAILED in the Vim source.

View File

@@ -2271,7 +2271,7 @@ int path_is_absolute(const char_u *fname)
void path_guess_exepath(const char *argv0, char *buf, size_t bufsize) void path_guess_exepath(const char *argv0, char *buf, size_t bufsize)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_ALL
{ {
char *path = getenv("PATH"); const char *path = os_getenv("PATH");
if (path == NULL || path_is_absolute((char_u *)argv0)) { if (path == NULL || path_is_absolute((char_u *)argv0)) {
xstrlcpy(buf, argv0, bufsize); xstrlcpy(buf, argv0, bufsize);

View File

@@ -2,13 +2,16 @@ local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq local eq = helpers.eq
local clear = helpers.clear local clear = helpers.clear
local command = helpers.command
local eval = helpers.eval
local meths = helpers.meths local meths = helpers.meths
local redir_exec = helpers.redir_exec local redir_exec = helpers.redir_exec
local source = helpers.source local source = helpers.source
local nvim_dir = helpers.nvim_dir
before_each(clear) before_each(clear)
describe(':let command', function() describe(':let', function()
it('correctly lists variables with curly-braces', function() it('correctly lists variables with curly-braces', function()
meths.set_var('v', {0}) meths.set_var('v', {0})
eq('\nv [0]', redir_exec('let {"v"}')) eq('\nv [0]', redir_exec('let {"v"}'))
@@ -42,4 +45,38 @@ describe(':let command', function()
call feedkeys(":\e:echo l1 l3\n:echo 42\n:cq\n", "t") call feedkeys(":\e:echo l1 l3\n:echo 42\n:cq\n", "t")
]=]) ]=])
end) end)
it("multibyte env var #8398 #9267", function()
command("let $NVIM_TEST = 'AìaB'")
eq('AìaB', eval('$NVIM_TEST'))
command("let $NVIM_TEST = 'AaあB'")
eq('AaあB', eval('$NVIM_TEST'))
local mbyte = [[\p* .ม .ม .ม .ม่ .ม่ .ม่ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ ֹֻ
.ֹֻ .ֹֻ .ֹֻ ֹֻ ֹֻ ֹֻ .ֹֻ .ֹֻ .ֹֻ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ
.ֹֻ .ֹֻ .ֹֻ a a a ca ca ca à à à]]
command("let $NVIM_TEST = '"..mbyte.."'")
eq(mbyte, eval('$NVIM_TEST'))
end)
it("multibyte env var to child process #8398 #9267", function()
if (not helpers.iswin()) and require('test.helpers').isCI() then
-- Fails on non-Windows CI. Buffering/timing issue?
pending('fails on unix CI', function() end)
end
local cmd_get_child_env = "let g:env_from_child = system(['"..nvim_dir.."/printenv-test', 'NVIM_TEST'])"
command("let $NVIM_TEST = 'AìaB'")
command(cmd_get_child_env)
eq(eval('$NVIM_TEST'), eval('g:env_from_child'))
command("let $NVIM_TEST = 'AaあB'")
command(cmd_get_child_env)
eq(eval('$NVIM_TEST'), eval('g:env_from_child'))
local mbyte = [[\p* .ม .ม .ม .ม่ .ม่ .ม่ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ ֹֻ
.ֹֻ .ֹֻ .ֹֻ ֹֻ ֹֻ ֹֻ .ֹֻ .ֹֻ .ֹֻ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ
.ֹֻ .ֹֻ .ֹֻ a a a ca ca ca à à à]]
command("let $NVIM_TEST = '"..mbyte.."'")
command(cmd_get_child_env)
eq(eval('$NVIM_TEST'), eval('g:env_from_child'))
end)
end) end)

View File

@@ -3,3 +3,7 @@ target_link_libraries(tty-test ${LIBUV_LIBRARIES})
add_executable(shell-test shell-test.c) add_executable(shell-test shell-test.c)
add_executable(printargs-test printargs-test.c) add_executable(printargs-test printargs-test.c)
add_executable(printenv-test printenv-test.c)
if(WIN32)
set_target_properties(printenv-test PROPERTIES LINK_FLAGS -municode)
endif()

View File

@@ -0,0 +1,59 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include <stdio.h>
#ifdef WIN32
# include <windows.h>
#else
# include <stdlib.h>
#endif
#ifdef WIN32
int wmain(int argc, wchar_t **argv)
#else
int main(int argc, char **argv)
#endif
{
if (argc != 2) {
return 1;
}
#ifdef WIN32
wchar_t *value = _wgetenv(argv[1]);
if (value == NULL) {
return 1;
}
int utf8_len = WideCharToMultiByte(CP_UTF8,
0,
value,
-1,
NULL,
0,
NULL,
NULL);
if (utf8_len == 0) {
return (int)GetLastError();
}
char *utf8_value = (char *)calloc((size_t)utf8_len, sizeof(char));
utf8_len = WideCharToMultiByte(CP_UTF8,
0,
value,
-1,
utf8_value,
utf8_len,
NULL,
NULL);
fprintf(stderr, "%s", utf8_value);
free(utf8_value);
#else
char *value = getenv(argv[1]);
if (value == NULL) {
fprintf(stderr, "env var not found: %s", argv[1]);
return 1;
}
// Print to stderr to avoid buffering.
fprintf(stderr, "%s", value);
#endif
return 0;
}

View File

@@ -708,7 +708,7 @@ end
local function isCI() local function isCI()
local is_travis = nil ~= os.getenv('TRAVIS') local is_travis = nil ~= os.getenv('TRAVIS')
local is_appveyor = nil ~= os.getenv('APPVEYOR') local is_appveyor = nil ~= os.getenv('APPVEYOR')
local is_quickbuild = nil ~= os.getenv('PR_NUMBER') local is_quickbuild = nil ~= lfs.attributes('/usr/home/quickbuild')
return is_travis or is_appveyor or is_quickbuild return is_travis or is_appveyor or is_quickbuild
end end
@@ -751,6 +751,7 @@ local module = {
hasenv = hasenv, hasenv = hasenv,
hexdump = hexdump, hexdump = hexdump,
intchar2lua = intchar2lua, intchar2lua = intchar2lua,
isCI = isCI,
map = map, map = map,
matches = matches, matches = matches,
mergedicts_copy = mergedicts_copy, mergedicts_copy = mergedicts_copy,

View File

@@ -645,16 +645,16 @@ local function itp_child(wr, func)
s = s:sub(1, hook_msglen - 2) s = s:sub(1, hook_msglen - 2)
sc.write(wr, '>' .. s .. (' '):rep(hook_msglen - 2 - #s) .. '\n') sc.write(wr, '>' .. s .. (' '):rep(hook_msglen - 2 - #s) .. '\n')
end end
local err, emsg = pcall(init) local status, result = pcall(init)
if err then if status then
collectgarbage('stop') collectgarbage('stop')
child_sethook(wr) child_sethook(wr)
err, emsg = pcall(func) status, result = pcall(func)
debug.sethook() debug.sethook()
end end
emsg = tostring(emsg)
sc.write(wr, trace_end_msg) sc.write(wr, trace_end_msg)
if not err then if not status then
local emsg = tostring(result)
if #emsg > 99999 then if #emsg > 99999 then
emsg = emsg:sub(1, 99999) emsg = emsg:sub(1, 99999)
end end
@@ -668,7 +668,7 @@ local function itp_child(wr, func)
collectgarbage() collectgarbage()
sc.write(wr, '$\n') sc.write(wr, '$\n')
sc.close(wr) sc.close(wr)
sc.exit(err and 0 or 1) sc.exit(status and 0 or 1)
end end
local function check_child_err(rd) local function check_child_err(rd)

View File

@@ -8,17 +8,22 @@ local ffi = helpers.ffi
local cstr = helpers.cstr local cstr = helpers.cstr
local to_cstr = helpers.to_cstr local to_cstr = helpers.to_cstr
local NULL = helpers.NULL local NULL = helpers.NULL
local OK = 0
require('lfs') require('lfs')
local cimp = cimport('./src/nvim/os/os.h') local cimp = cimport('./src/nvim/os/os.h')
describe('env.c', function() describe('env.c', function()
local function os_env_exists(name)
return cimp.os_env_exists(to_cstr(name))
end
local function os_setenv(name, value, override) local function os_setenv(name, value, override)
return cimp.os_setenv(to_cstr(name), to_cstr(value), override) return cimp.os_setenv(to_cstr(name), to_cstr(value), override)
end end
local function os_unsetenv(name, _, _) local function os_unsetenv(name)
return cimp.os_unsetenv(to_cstr(name)) return cimp.os_unsetenv(to_cstr(name))
end end
@@ -31,25 +36,44 @@ describe('env.c', function()
end end
end end
describe('os_setenv', function() itp('os_env_exists', function()
local OK = 0 eq(false, os_env_exists(''))
eq(false, os_env_exists(' '))
eq(false, os_env_exists('\t'))
eq(false, os_env_exists('\n'))
eq(false, os_env_exists('AaあB <= very weird name...'))
itp('sets an env variable and returns OK', function() local varname = 'NVIM_UNIT_TEST_os_env_exists'
eq(false, os_env_exists(varname))
eq(OK, os_setenv(varname, 'foo bar baz ...', 1))
eq(true, os_env_exists(varname))
end)
describe('os_setenv', function()
itp('sets an env var and returns success', function()
local name = 'NVIM_UNIT_TEST_SETENV_1N' local name = 'NVIM_UNIT_TEST_SETENV_1N'
local value = 'NVIM_UNIT_TEST_SETENV_1V' local value = 'NVIM_UNIT_TEST_SETENV_1V'
eq(nil, os.getenv(name)) eq(nil, os.getenv(name))
eq(OK, (os_setenv(name, value, 1))) eq(OK, os_setenv(name, value, 1))
eq(value, os.getenv(name)) eq(value, os.getenv(name))
-- Set empty, then set non-empty, then retrieve.
eq(OK, os_setenv(name, '', 1))
eq('', os.getenv(name))
eq(OK, os_setenv(name, 'non-empty', 1))
eq('non-empty', os.getenv(name))
end) end)
itp("dosn't overwrite an env variable if overwrite is 0", function() itp("`overwrite` behavior", function()
local name = 'NVIM_UNIT_TEST_SETENV_2N' local name = 'NVIM_UNIT_TEST_SETENV_2N'
local value = 'NVIM_UNIT_TEST_SETENV_2V' local value = 'NVIM_UNIT_TEST_SETENV_2V'
local value_updated = 'NVIM_UNIT_TEST_SETENV_2V_UPDATED' local value_updated = 'NVIM_UNIT_TEST_SETENV_2V_UPDATED'
eq(OK, (os_setenv(name, value, 0))) eq(OK, os_setenv(name, value, 0))
eq(value, os.getenv(name)) eq(value, os.getenv(name))
eq(OK, (os_setenv(name, value_updated, 0))) eq(OK, os_setenv(name, value_updated, 0))
eq(value, os.getenv(name)) eq(value, os.getenv(name))
eq(OK, os_setenv(name, value_updated, 1))
eq(value_updated, os.getenv(name))
end) end)
end) end)
@@ -93,31 +117,42 @@ describe('env.c', function()
end) end)
describe('os_getenv', function() describe('os_getenv', function()
itp('reads an env variable', function() itp('reads an env var', function()
local name = 'NVIM_UNIT_TEST_GETENV_1N' local name = 'NVIM_UNIT_TEST_GETENV_1N'
local value = 'NVIM_UNIT_TEST_GETENV_1V' local value = 'NVIM_UNIT_TEST_GETENV_1V'
eq(NULL, os_getenv(name)) eq(NULL, os_getenv(name))
-- Use os_setenv because Lua dosen't have setenv. -- Use os_setenv because Lua dosen't have setenv.
os_setenv(name, value, 1) os_setenv(name, value, 1)
eq(value, os_getenv(name)) eq(value, os_getenv(name))
-- Get a big value.
local bigval = ('x'):rep(256)
eq(OK, os_setenv(name, bigval, 1))
eq(bigval, os_getenv(name))
-- Set non-empty, then set empty.
eq(OK, os_setenv(name, 'non-empty', 1))
eq('non-empty', os_getenv(name))
eq(OK, os_setenv(name, '', 1))
eq(NULL, os_getenv(name))
end) end)
itp('returns NULL if the env variable is not found', function() itp('returns NULL if the env var is not found', function()
local name = 'NVIM_UNIT_TEST_GETENV_NOTFOUND' eq(NULL, os_getenv('NVIM_UNIT_TEST_GETENV_NOTFOUND'))
return eq(NULL, os_getenv(name))
end) end)
end) end)
describe('os_unsetenv', function() itp('os_unsetenv', function()
itp('unsets environment variable', function()
local name = 'TEST_UNSETENV' local name = 'TEST_UNSETENV'
local value = 'TESTVALUE' local value = 'TESTVALUE'
os_setenv(name, value, 1) os_setenv(name, value, 1)
os_unsetenv(name) eq(OK, os_unsetenv(name))
neq(os_getenv(name), value) neq(os_getenv(name), value)
-- Depending on the platform the var might be unset or set as '' -- Depending on the platform the var might be unset or set as ''
assert.True(os_getenv(name) == nil or os_getenv(name) == '') assert.True(os_getenv(name) == nil or os_getenv(name) == '')
end) if os_getenv(name) == nil then
eq(false, os_env_exists(name))
end
end) end)
describe('os_getenvname_at_index', function() describe('os_getenvname_at_index', function()