diff options
Diffstat (limited to 'src/nvim/os')
-rw-r--r-- | src/nvim/os/env.c | 207 | ||||
-rw-r--r-- | src/nvim/os/fs.c | 69 | ||||
-rw-r--r-- | src/nvim/os/input.c | 9 | ||||
-rw-r--r-- | src/nvim/os/os_win_console.c | 74 | ||||
-rw-r--r-- | src/nvim/os/os_win_console.h | 12 | ||||
-rw-r--r-- | src/nvim/os/pty_conpty_win.c | 199 | ||||
-rw-r--r-- | src/nvim/os/pty_conpty_win.h | 22 | ||||
-rw-r--r-- | src/nvim/os/pty_process_win.c | 136 | ||||
-rw-r--r-- | src/nvim/os/pty_process_win.h | 15 | ||||
-rw-r--r-- | src/nvim/os/shell.c | 501 | ||||
-rw-r--r-- | src/nvim/os/signal.c | 34 | ||||
-rw-r--r-- | src/nvim/os/time.c | 55 | ||||
-rw-r--r-- | src/nvim/os/tty.c | 5 | ||||
-rw-r--r-- | src/nvim/os/users.c | 83 |
14 files changed, 1306 insertions, 115 deletions
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index f5dbf0694e..082ad58223 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -44,6 +44,16 @@ void env_init(void) uv_mutex_init(&mutex); } +void os_env_var_lock(void) +{ + uv_mutex_lock(&mutex); +} + +void os_env_var_unlock(void) +{ + uv_mutex_unlock(&mutex); +} + /// Like getenv(), but returns NULL if the variable is empty. /// @see os_env_exists const char *os_getenv(const char *name) @@ -55,6 +65,7 @@ const char *os_getenv(const char *name) return NULL; } uv_mutex_lock(&mutex); + int r = 0; if (pmap_has(cstr_t)(envmap, name) && !!(e = (char *)pmap_get(cstr_t)(envmap, name))) { if (e[0] != '\0') { @@ -67,7 +78,7 @@ const char *os_getenv(const char *name) pmap_del2(envmap, name); } e = xmalloc(size); - int r = uv_os_getenv(name, e, &size); + r = uv_os_getenv(name, e, &size); if (r == UV_ENOBUFS) { e = xrealloc(e, size); r = uv_os_getenv(name, e, &size); @@ -75,14 +86,15 @@ const char *os_getenv(const char *name) 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: + // Must do this before ELOG, log.c may call os_setenv. uv_mutex_unlock(&mutex); + if (r != 0 && r != UV_ENOENT && r != UV_UNKNOWN) { + ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r)); + } return (e == NULL || size == 0 || e[0] == '\0') ? NULL : e; } @@ -102,9 +114,6 @@ bool os_env_exists(const char *name) 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)); -#ifdef WIN32 - return (r == UV_UNKNOWN); -#endif } return (r == 0 || r == UV_ENOBUFS); } @@ -135,15 +144,25 @@ int os_setenv(const char *name, const char *value, int overwrite) } #endif uv_mutex_lock(&mutex); - int r = uv_os_setenv(name, value); + int r; +#ifdef WIN32 + // libintl uses getenv() for LC_ALL/LANG/etc., so we must use _putenv_s(). + if (striequal(name, "LC_ALL") || striequal(name, "LANGUAGE") + || striequal(name, "LANG") || striequal(name, "LC_MESSAGES")) { + r = _putenv_s(name, value); // NOLINT + assert(r == 0); + } +#endif + r = uv_os_setenv(name, value); assert(r != UV_EINVAL); // Destroy the old map item. Do this AFTER uv_os_setenv(), because `value` // could be a previous os_getenv() result. pmap_del2(envmap, name); + // Must do this before ELOG, log.c may call os_setenv. + uv_mutex_unlock(&mutex); 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; } @@ -157,46 +176,148 @@ int os_unsetenv(const char *name) uv_mutex_lock(&mutex); pmap_del2(envmap, name); int r = uv_os_unsetenv(name); + // Must do this before ELOG, log.c may call os_setenv. + uv_mutex_unlock(&mutex); 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; } +/// Returns number of variables in the current environment variables block +size_t os_get_fullenv_size(void) +{ + size_t len = 0; +#ifdef _WIN32 + wchar_t *envstrings = GetEnvironmentStringsW(); + wchar_t *p = envstrings; + size_t l; + if (!envstrings) { + return len; + } + // GetEnvironmentStringsW() result has this format: + // var1=value1\0var2=value2\0...varN=valueN\0\0 + while ((l = wcslen(p)) != 0) { + p += l + 1; + len++; + } + + FreeEnvironmentStringsW(envstrings); +#else +# if defined(HAVE__NSGETENVIRON) + char **environ = *_NSGetEnviron(); +# else + extern char **environ; +# endif + + while (environ[len] != NULL) { + len++; + } + +#endif + return len; +} + +void os_free_fullenv(char **env) +{ + if (!env) { return; } + for (char **it = env; *it; it++) { + XFREE_CLEAR(*it); + } + xfree(env); +} + +/// Copies the current environment variables into the given array, `env`. Each +/// array element is of the form "NAME=VALUE". +/// Result must be freed by the caller. +/// +/// @param[out] env array to populate with environment variables +/// @param env_size size of `env`, @see os_fullenv_size +void os_copy_fullenv(char **env, size_t env_size) +{ +#ifdef _WIN32 + wchar_t *envstrings = GetEnvironmentStringsW(); + if (!envstrings) { + return; + } + wchar_t *p = envstrings; + size_t i = 0; + size_t l; + // GetEnvironmentStringsW() result has this format: + // var1=value1\0var2=value2\0...varN=valueN\0\0 + while ((l = wcslen(p)) != 0 && i < env_size) { + char *utf8_str; + int conversion_result = utf16_to_utf8(p, -1, &utf8_str); + if (conversion_result != 0) { + EMSG2("utf16_to_utf8 failed: %d", conversion_result); + break; + } + p += l + 1; + + env[i] = utf8_str; + i++; + } + + FreeEnvironmentStringsW(envstrings); +#else +# if defined(HAVE__NSGETENVIRON) + char **environ = *_NSGetEnviron(); +# else + extern char **environ; +# endif + + for (size_t i = 0; i < env_size && environ[i] != NULL; i++) { + env[i] = xstrdup(environ[i]); + } +#endif +} + +/// Copy value of the environment variable at `index` in the current +/// environment variables block. +/// Result must be freed by the caller. +/// +/// @param index nth item in environment variables block +/// @return [allocated] environment variable's value, or NULL char *os_getenvname_at_index(size_t index) { #ifdef _WIN32 - wchar_t *env = GetEnvironmentStringsW(); - if (!env) { + wchar_t *envstrings = GetEnvironmentStringsW(); + if (!envstrings) { return NULL; } + wchar_t *p = envstrings; char *name = NULL; - size_t current_index = 0; + size_t i = 0; + size_t l; // GetEnvironmentStringsW() result has this format: // var1=value1\0var2=value2\0...varN=valueN\0\0 - for (wchar_t *it = env; *it != L'\0' || *(it + 1) != L'\0'; it++) { - if (index == current_index) { + while ((l = wcslen(p)) != 0 && i <= index) { + if (i == index) { char *utf8_str; - int conversion_result = utf16_to_utf8(it, -1, &utf8_str); + int conversion_result = utf16_to_utf8(p, -1, &utf8_str); if (conversion_result != 0) { EMSG2("utf16_to_utf8 failed: %d", conversion_result); break; } - size_t namesize = 0; - while (utf8_str[namesize] != '=' && utf8_str[namesize] != NUL) { - namesize++; - } - name = (char *)vim_strnsave((char_u *)utf8_str, namesize); + + // Some Windows env vars start with =, so skip over that to find the + // separator between name/value + const char * const end = strchr(utf8_str + (utf8_str[0] == '=' ? 1 : 0), + '='); + assert(end != NULL); + ptrdiff_t len = end - utf8_str; + assert(len > 0); + name = xstrndup(utf8_str, (size_t)len); xfree(utf8_str); break; } - if (*it == L'\0') { - current_index++; - } + + // Advance past the name and NUL + p += l + 1; + i++; } - FreeEnvironmentStringsW(env); + FreeEnvironmentStringsW(envstrings); return name; #else # if defined(HAVE__NSGETENVIRON) @@ -204,19 +325,20 @@ char *os_getenvname_at_index(size_t index) # else extern char **environ; # endif - // Check if index is inside the environ array and is not the last element. + + // check if index is inside the environ array for (size_t i = 0; i <= index; i++) { if (environ[i] == NULL) { return NULL; } } char *str = environ[index]; - size_t namesize = 0; - while (str[namesize] != '=' && str[namesize] != NUL) { - namesize++; - } - char *name = (char *)vim_strnsave((char_u *)str, namesize); - return name; + assert(str != NULL); + const char * const end = strchr(str, '='); + assert(end != NULL); + ptrdiff_t len = end - str; + assert(len > 0); + return xstrndup(str, (size_t)len); #endif } @@ -725,6 +847,20 @@ const void *vim_env_iter_rev(const char delim, } } + +/// @param[out] exe_name should be at least MAXPATHL in size +void vim_get_prefix_from_exepath(char *exe_name) +{ + // TODO(bfredl): param could have been written as "char exe_name[MAXPATHL]" + // but c_grammar.lua does not recognize it (yet). + xstrlcpy(exe_name, (char *)get_vim_var_str(VV_PROGPATH), + MAXPATHL * sizeof(*exe_name)); + char *path_end = (char *)path_tail_with_sep((char_u *)exe_name); + *path_end = '\0'; // remove the trailing "nvim.exe" + path_end = (char *)path_tail((char_u *)exe_name); + *path_end = '\0'; // remove the trailing "bin/" +} + /// Vim getenv() wrapper with special handling of $HOME, $VIM, $VIMRUNTIME, /// allowing the user to override the Nvim runtime directory at runtime. /// Result must be freed by the caller. @@ -780,12 +916,7 @@ char *vim_getenv(const char *name) char exe_name[MAXPATHL]; // Find runtime path relative to the nvim binary: ../share/nvim/runtime if (vim_path == NULL) { - xstrlcpy(exe_name, (char *)get_vim_var_str(VV_PROGPATH), - sizeof(exe_name)); - char *path_end = (char *)path_tail_with_sep((char_u *)exe_name); - *path_end = '\0'; // remove the trailing "nvim.exe" - path_end = (char *)path_tail((char_u *)exe_name); - *path_end = '\0'; // remove the trailing "bin/" + vim_get_prefix_from_exepath(exe_name); if (append_path( exe_name, "share" _PATHSEPSTR "nvim" _PATHSEPSTR "runtime" _PATHSEPSTR, diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 6d76cc3613..a3bef3389c 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -1159,6 +1159,30 @@ bool os_fileid_equal_fileinfo(const FileID *file_id, && file_id->device_id == file_info->stat.st_dev; } +/// Return the canonicalized absolute pathname. +/// +/// @param[in] name Filename to be canonicalized. +/// @param[out] buf Buffer to store the canonicalized values. A minimum length +// of MAXPATHL+1 is required. If it is NULL, memory is +// allocated. In that case, the caller should deallocate this +// buffer. +/// +/// @return pointer to the buf on success, or NULL. +char *os_realpath(const char *name, char *buf) + FUNC_ATTR_NONNULL_ARG(1) +{ + uv_fs_t request; + int result = uv_fs_realpath(&fs_loop, &request, name, NULL); + if (result == kLibuvSuccess) { + if (buf == NULL) { + buf = xmallocz(MAXPATHL); + } + xstrlcpy(buf, request.ptr, MAXPATHL + 1); + } + uv_fs_req_cleanup(&request); + return result == kLibuvSuccess ? buf : NULL; +} + #ifdef WIN32 # include <shlobj.h> @@ -1245,4 +1269,49 @@ shortcut_end: return rfname; } +#define is_path_sep(c) ((c) == L'\\' || (c) == L'/') +/// Returns true if the path contains a reparse point (junction or symbolic +/// link). Otherwise false in returned. +bool os_is_reparse_point_include(const char *path) +{ + wchar_t *p, *q, *utf16_path; + wchar_t buf[MAX_PATH]; + DWORD attr; + bool result = false; + + const int r = utf8_to_utf16(path, -1, &utf16_path); + if (r != 0) { + EMSG2("utf8_to_utf16 failed: %d", r); + return false; + } + + p = utf16_path; + if (isalpha(p[0]) && p[1] == L':' && is_path_sep(p[2])) { + p += 3; + } else if (is_path_sep(p[0]) && is_path_sep(p[1])) { + p += 2; + } + + while (*p != L'\0') { + q = wcspbrk(p, L"\\/"); + if (q == NULL) { + p = q = utf16_path + wcslen(utf16_path); + } else { + p = q + 1; + } + if (q - utf16_path >= MAX_PATH) { + break; + } + wcsncpy(buf, utf16_path, (size_t)(q - utf16_path)); + buf[q - utf16_path] = L'\0'; + attr = GetFileAttributesW(buf); + if (attr != INVALID_FILE_ATTRIBUTES + && (attr & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { + result = true; + break; + } + } + xfree(utf16_path); + return result; +} #endif diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index c1580c5fc3..b7878d9da8 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -188,8 +188,13 @@ size_t input_enqueue(String keys) char *ptr = keys.data; char *end = ptr + keys.size; - while (rbuffer_space(input_buffer) >= 6 && ptr < end) { - uint8_t buf[6] = { 0 }; + while (rbuffer_space(input_buffer) >= 19 && ptr < end) { + // A "<x>" form occupies at least 1 characters, and produces up + // to 19 characters (1 + 5 * 3 for the char and 3 for a modifier). + // In the case of K_SPECIAL(0x80) or CSI(0x9B), 3 bytes are escaped and + // needed, but since the keys are UTF-8, so the first byte cannot be + // K_SPECIAL(0x80) or CSI(0x9B). + uint8_t buf[19] = { 0 }; unsigned int new_size = trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, true, false); diff --git a/src/nvim/os/os_win_console.c b/src/nvim/os/os_win_console.c new file mode 100644 index 0000000000..18dcfeafa0 --- /dev/null +++ b/src/nvim/os/os_win_console.c @@ -0,0 +1,74 @@ +// 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 "nvim/vim.h" +#include "nvim/os/input.h" +#include "nvim/os/os_win_console.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/os_win_console.c.generated.h" +#endif + + +int os_get_conin_fd(void) +{ + const HANDLE conin_handle = CreateFile("CONIN$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + (LPSECURITY_ATTRIBUTES)NULL, + OPEN_EXISTING, 0, (HANDLE)NULL); + assert(conin_handle != INVALID_HANDLE_VALUE); + int conin_fd = _open_osfhandle((intptr_t)conin_handle, _O_RDONLY); + assert(conin_fd != -1); + return conin_fd; +} + +void os_replace_stdin_to_conin(void) +{ + close(STDIN_FILENO); + const int conin_fd = os_get_conin_fd(); + assert(conin_fd == STDIN_FILENO); +} + +void os_replace_stdout_and_stderr_to_conout(void) +{ + const HANDLE conout_handle = + CreateFile("CONOUT$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + (LPSECURITY_ATTRIBUTES)NULL, + OPEN_EXISTING, 0, (HANDLE)NULL); + assert(conout_handle != INVALID_HANDLE_VALUE); + close(STDOUT_FILENO); + const int conout_fd = _open_osfhandle((intptr_t)conout_handle, 0); + assert(conout_fd == STDOUT_FILENO); + close(STDERR_FILENO); + const int conerr_fd = _open_osfhandle((intptr_t)conout_handle, 0); + assert(conerr_fd == STDERR_FILENO); +} + +void os_set_vtp(bool enable) +{ + static TriState is_legacy = kNone; + if (is_legacy == kNone) { + uv_tty_vtermstate_t state; + uv_tty_get_vterm_state(&state); + is_legacy = (state == UV_TTY_UNSUPPORTED) ? kTrue : kFalse; + } + if (!is_legacy && !os_has_vti()) { + uv_tty_set_vterm_state(enable ? UV_TTY_SUPPORTED : UV_TTY_UNSUPPORTED); + } +} + +static bool os_has_vti(void) +{ + static TriState has_vti = kNone; + if (has_vti == kNone) { + HANDLE handle = (HANDLE)_get_osfhandle(input_global_fd()); + DWORD dwMode; + if (handle != INVALID_HANDLE_VALUE && GetConsoleMode(handle, &dwMode)) { + has_vti = !!(dwMode & ENABLE_VIRTUAL_TERMINAL_INPUT) ? kTrue : kFalse; + } + } + return has_vti == kTrue; +} diff --git a/src/nvim/os/os_win_console.h b/src/nvim/os/os_win_console.h new file mode 100644 index 0000000000..7b5800afa8 --- /dev/null +++ b/src/nvim/os/os_win_console.h @@ -0,0 +1,12 @@ +#ifndef NVIM_OS_OS_WIN_CONSOLE_H +#define NVIM_OS_OS_WIN_CONSOLE_H + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/os_win_console.h.generated.h" +#endif + +#ifndef ENABLE_VIRTUAL_TERMINAL_INPUT +# define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 +#endif + +#endif // NVIM_OS_OS_WIN_CONSOLE_H diff --git a/src/nvim/os/pty_conpty_win.c b/src/nvim/os/pty_conpty_win.c new file mode 100644 index 0000000000..5bcadd6490 --- /dev/null +++ b/src/nvim/os/pty_conpty_win.c @@ -0,0 +1,199 @@ +// 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 <uv.h> + +#include "nvim/vim.h" +#include "nvim/os/os.h" +#include "nvim/os/pty_conpty_win.h" + +#ifndef EXTENDED_STARTUPINFO_PRESENT +# define EXTENDED_STARTUPINFO_PRESENT 0x00080000 +#endif +#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE +# define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016 +#endif + +HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON *); +HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD); +void (WINAPI *pClosePseudoConsole)(HPCON); + +bool os_has_conpty_working(void) +{ + static TriState has_conpty = kNone; + if (has_conpty == kNone) { + has_conpty = os_dyn_conpty_init(); + } + + return has_conpty == kTrue; +} + +TriState os_dyn_conpty_init(void) +{ + uv_lib_t kernel; + if (uv_dlopen("kernel32.dll", &kernel)) { + uv_dlclose(&kernel); + return kFalse; + } + static struct { + char *name; + FARPROC *ptr; + } conpty_entry[] = { + { "CreatePseudoConsole", (FARPROC *)&pCreatePseudoConsole }, + { "ResizePseudoConsole", (FARPROC *)&pResizePseudoConsole }, + { "ClosePseudoConsole", (FARPROC *)&pClosePseudoConsole }, + { NULL, NULL } + }; + for (int i = 0; + conpty_entry[i].name != NULL && conpty_entry[i].ptr != NULL; i++) { + if (uv_dlsym(&kernel, conpty_entry[i].name, (void **)conpty_entry[i].ptr)) { + uv_dlclose(&kernel); + return kFalse; + } + } + return kTrue; +} + +conpty_t *os_conpty_init(char **in_name, char **out_name, + uint16_t width, uint16_t height) +{ + static int count = 0; + conpty_t *conpty_object = xcalloc(1, sizeof(*conpty_object)); + const char *emsg = NULL; + HANDLE in_read = INVALID_HANDLE_VALUE; + HANDLE out_write = INVALID_HANDLE_VALUE; + char buf[MAXPATHL]; + SECURITY_ATTRIBUTES sa = { 0 }; + const DWORD mode = PIPE_ACCESS_INBOUND + | PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE; + + sa.nLength = sizeof(sa); + snprintf(buf, sizeof(buf), "\\\\.\\pipe\\nvim-term-in-%d-%d", + os_get_pid(), count); + *in_name = xstrdup(buf); + if ((in_read = CreateNamedPipeA( + *in_name, + mode, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + 1, + 0, + 0, + 30000, + &sa)) == INVALID_HANDLE_VALUE) { + emsg = "create input pipe failed"; + goto failed; + } + snprintf(buf, sizeof(buf), "\\\\.\\pipe\\nvim-term-out-%d-%d", + os_get_pid(), count); + *out_name = xstrdup(buf); + if ((out_write = CreateNamedPipeA( + *out_name, + mode, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + 1, + 0, + 0, + 30000, + &sa)) == INVALID_HANDLE_VALUE) { + emsg = "create output pipe failed"; + goto failed; + } + assert(width <= SHRT_MAX); + assert(height <= SHRT_MAX); + COORD size = { (int16_t)width, (int16_t)height }; + HRESULT hr; + hr = pCreatePseudoConsole(size, in_read, out_write, 0, &conpty_object->pty); + if (FAILED(hr)) { + emsg = "create psudo console failed"; + goto failed; + } + + conpty_object->si_ex.StartupInfo.cb = sizeof(conpty_object->si_ex); + size_t bytes_required; + InitializeProcThreadAttributeList(NULL, 1, 0, & bytes_required); + conpty_object->si_ex.lpAttributeList = + (PPROC_THREAD_ATTRIBUTE_LIST)xmalloc(bytes_required); + if (!InitializeProcThreadAttributeList( + conpty_object->si_ex.lpAttributeList, + 1, + 0, + &bytes_required)) { + emsg = "InitializeProcThreadAttributeList failed"; + goto failed; + } + if (!UpdateProcThreadAttribute( + conpty_object->si_ex.lpAttributeList, + 0, + PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, + conpty_object->pty, + sizeof(conpty_object->pty), + NULL, + NULL)) { + emsg = "UpdateProcThreadAttribute failed"; + goto failed; + } + count++; + goto finished; + +failed: + ELOG("os_conpty_init:%s : error code: %d", + emsg, os_translate_sys_error((int)GetLastError())); + os_conpty_free(conpty_object); + conpty_object = NULL; +finished: + if (in_read != INVALID_HANDLE_VALUE) { + CloseHandle(in_read); + } + if (out_write != INVALID_HANDLE_VALUE) { + CloseHandle(out_write); + } + return conpty_object; +} + +bool os_conpty_spawn(conpty_t *conpty_object, HANDLE *process_handle, + wchar_t *name, wchar_t *cmd_line, wchar_t *cwd, + wchar_t *env) +{ + PROCESS_INFORMATION pi = { 0 }; + if (!CreateProcessW( + name, + cmd_line, + NULL, + NULL, + false, + EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, + env, + cwd, + &conpty_object->si_ex.StartupInfo, + &pi)) { + return false; + } + *process_handle = pi.hProcess; + return true; +} + +void os_conpty_set_size(conpty_t *conpty_object, + uint16_t width, uint16_t height) +{ + assert(width <= SHRT_MAX); + assert(height <= SHRT_MAX); + COORD size = { (int16_t)width, (int16_t)height }; + if (pResizePseudoConsole(conpty_object->pty, size) != S_OK) { + ELOG("ResizePseudoConsoel failed: error code: %d", + os_translate_sys_error((int)GetLastError())); + } +} + +void os_conpty_free(conpty_t *conpty_object) +{ + if (conpty_object != NULL) { + if (conpty_object->si_ex.lpAttributeList != NULL) { + DeleteProcThreadAttributeList(conpty_object->si_ex.lpAttributeList); + xfree(conpty_object->si_ex.lpAttributeList); + } + if (conpty_object->pty != NULL) { + pClosePseudoConsole(conpty_object->pty); + } + } + xfree(conpty_object); +} diff --git a/src/nvim/os/pty_conpty_win.h b/src/nvim/os/pty_conpty_win.h new file mode 100644 index 0000000000..c243db4fa5 --- /dev/null +++ b/src/nvim/os/pty_conpty_win.h @@ -0,0 +1,22 @@ +#ifndef NVIM_OS_PTY_CONPTY_WIN_H +#define NVIM_OS_PTY_CONPTY_WIN_H + +#ifndef HPCON +# define HPCON VOID * +#endif + +extern HRESULT (WINAPI *pCreatePseudoConsole) // NOLINT(whitespace/parens) + (COORD, HANDLE, HANDLE, DWORD, HPCON *); +extern HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD); +extern void (WINAPI *pClosePseudoConsole)(HPCON); + +typedef struct conpty { + HPCON pty; + STARTUPINFOEXW si_ex; +} conpty_t; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/pty_conpty_win.h.generated.h" +#endif + +#endif // NVIM_OS_PTY_CONPTY_WIN_H diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index 183219bd3e..6f7100e846 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -12,6 +12,7 @@ #include "nvim/memory.h" #include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8 #include "nvim/os/pty_process_win.h" +#include "nvim/os/pty_conpty_win.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/pty_process_win.c.generated.h" @@ -23,6 +24,11 @@ static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused) PtyProcess *ptyproc = (PtyProcess *)context; Process *proc = (Process *)ptyproc; + if (ptyproc->type == kConpty + && ptyproc->object.conpty != NULL) { + os_conpty_free(ptyproc->object.conpty); + ptyproc->object.conpty = NULL; + } uv_timer_init(&proc->loop->uv, &ptyproc->wait_eof_timer); ptyproc->wait_eof_timer.data = (void *)ptyproc; uv_timer_start(&ptyproc->wait_eof_timer, wait_eof_timer_cb, 200, 200); @@ -38,6 +44,7 @@ int pty_process_spawn(PtyProcess *ptyproc) winpty_config_t *cfg = NULL; winpty_spawn_config_t *spawncfg = NULL; winpty_t *winpty_object = NULL; + conpty_t *conpty_object = NULL; char *in_name = NULL; char *out_name = NULL; HANDLE process_handle = NULL; @@ -49,29 +56,39 @@ int pty_process_spawn(PtyProcess *ptyproc) assert(proc->err.closed); - cfg = winpty_config_new(WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION, &err); - if (cfg == NULL) { - emsg = "winpty_config_new failed"; - goto cleanup; + if (os_has_conpty_working()) { + if ((conpty_object = + os_conpty_init(&in_name, &out_name, + ptyproc->width, ptyproc->height)) != NULL) { + ptyproc->type = kConpty; + } } - winpty_config_set_initial_size(cfg, ptyproc->width, ptyproc->height); - winpty_object = winpty_open(cfg, &err); - if (winpty_object == NULL) { - emsg = "winpty_open failed"; - goto cleanup; - } + if (ptyproc->type == kWinpty) { + cfg = winpty_config_new(WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION, &err); + if (cfg == NULL) { + emsg = "winpty_config_new failed"; + goto cleanup; + } - status = utf16_to_utf8(winpty_conin_name(winpty_object), -1, &in_name); - if (status != 0) { - emsg = "utf16_to_utf8(winpty_conin_name) failed"; - goto cleanup; - } + winpty_config_set_initial_size(cfg, ptyproc->width, ptyproc->height); + winpty_object = winpty_open(cfg, &err); + if (winpty_object == NULL) { + emsg = "winpty_open failed"; + goto cleanup; + } - status = utf16_to_utf8(winpty_conout_name(winpty_object), -1, &out_name); - if (status != 0) { - emsg = "utf16_to_utf8(winpty_conout_name) failed"; - goto cleanup; + status = utf16_to_utf8(winpty_conin_name(winpty_object), -1, &in_name); + if (status != 0) { + emsg = "utf16_to_utf8(winpty_conin_name) failed"; + goto cleanup; + } + + status = utf16_to_utf8(winpty_conout_name(winpty_object), -1, &out_name); + if (status != 0) { + emsg = "utf16_to_utf8(winpty_conout_name) failed"; + goto cleanup; + } } if (!proc->in.closed) { @@ -107,32 +124,45 @@ int pty_process_spawn(PtyProcess *ptyproc) goto cleanup; } - spawncfg = winpty_spawn_config_new( - WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, - NULL, // Optional application name - cmd_line, - cwd, - NULL, // Optional environment variables - &err); - if (spawncfg == NULL) { - emsg = "winpty_spawn_config_new failed"; - goto cleanup; - } + if (ptyproc->type == kConpty) { + if (!os_conpty_spawn(conpty_object, + &process_handle, + NULL, + cmd_line, + cwd, + NULL)) { + emsg = "os_conpty_spawn failed"; + status = (int)GetLastError(); + goto cleanup; + } + } else { + spawncfg = winpty_spawn_config_new( + WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, + NULL, // Optional application name + cmd_line, + cwd, + NULL, // Optional environment variables + &err); + if (spawncfg == NULL) { + emsg = "winpty_spawn_config_new failed"; + goto cleanup; + } - DWORD win_err = 0; - if (!winpty_spawn(winpty_object, - spawncfg, - &process_handle, - NULL, // Optional thread handle - &win_err, - &err)) { - if (win_err) { - status = (int)win_err; - emsg = "failed to spawn process"; - } else { - emsg = "winpty_spawn failed"; + DWORD win_err = 0; + if (!winpty_spawn(winpty_object, + spawncfg, + &process_handle, + NULL, // Optional thread handle + &win_err, + &err)) { + if (win_err) { + status = (int)win_err; + emsg = "failed to spawn process"; + } else { + emsg = "winpty_spawn failed"; + } + goto cleanup; } - goto cleanup; } proc->pid = (int)GetProcessId(process_handle); @@ -152,9 +182,12 @@ int pty_process_spawn(PtyProcess *ptyproc) uv_run(&proc->loop->uv, UV_RUN_ONCE); } - ptyproc->winpty_object = winpty_object; + (ptyproc->type == kConpty) ? + (void *)(ptyproc->object.conpty = conpty_object) : + (void *)(ptyproc->object.winpty = winpty_object); ptyproc->process_handle = process_handle; winpty_object = NULL; + conpty_object = NULL; process_handle = NULL; cleanup: @@ -171,6 +204,7 @@ cleanup: winpty_config_free(cfg); winpty_spawn_config_free(spawncfg); winpty_free(winpty_object); + os_conpty_free(conpty_object); xfree(in_name); xfree(out_name); if (process_handle != NULL) { @@ -192,8 +226,11 @@ void pty_process_resize(PtyProcess *ptyproc, uint16_t width, uint16_t height) FUNC_ATTR_NONNULL_ALL { - if (ptyproc->winpty_object != NULL) { - winpty_set_size(ptyproc->winpty_object, width, height, NULL); + if (ptyproc->type == kConpty + && ptyproc->object.conpty != NULL) { + os_conpty_set_size(ptyproc->object.conpty, width, height); + } else if (ptyproc->object.winpty != NULL) { + winpty_set_size(ptyproc->object.winpty, width, height, NULL); } } @@ -212,9 +249,10 @@ void pty_process_close(PtyProcess *ptyproc) void pty_process_close_master(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL { - if (ptyproc->winpty_object != NULL) { - winpty_free(ptyproc->winpty_object); - ptyproc->winpty_object = NULL; + if (ptyproc->type == kWinpty + && ptyproc->object.winpty != NULL) { + winpty_free(ptyproc->object.winpty); + ptyproc->object.winpty = NULL; } } diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h index 1a4019e654..8ad5ba7286 100644 --- a/src/nvim/os/pty_process_win.h +++ b/src/nvim/os/pty_process_win.h @@ -6,12 +6,22 @@ #include "nvim/event/process.h" #include "nvim/lib/queue.h" +#include "nvim/os/pty_conpty_win.h" + +typedef enum { + kWinpty, + kConpty +} PtyType; typedef struct pty_process { Process process; char *term_name; uint16_t width, height; - winpty_t *winpty_object; + union { + winpty_t *winpty; + conpty_t *conpty; + } object; + PtyType type; HANDLE finish_wait; HANDLE process_handle; uv_timer_t wait_eof_timer; @@ -30,7 +40,8 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data) rv.term_name = NULL; rv.width = 80; rv.height = 24; - rv.winpty_object = NULL; + rv.object.winpty = NULL; + rv.type = kWinpty; rv.finish_wait = NULL; rv.process_handle = NULL; return rv; diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 6956410401..6294d5e4e2 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -9,13 +9,17 @@ #include <uv.h> #include "nvim/ascii.h" +#include "nvim/fileio.h" #include "nvim/lib/kvec.h" #include "nvim/log.h" #include "nvim/event/loop.h" #include "nvim/event/libuv_process.h" #include "nvim/event/rstream.h" +#include "nvim/ex_cmds.h" +#include "nvim/misc1.h" #include "nvim/os/shell.h" #include "nvim/os/signal.h" +#include "nvim/path.h" #include "nvim/types.h" #include "nvim/main.h" #include "nvim/vim.h" @@ -32,6 +36,8 @@ #define NS_1_SECOND 1000000000U // 1 second, in nanoseconds #define OUT_DATA_THRESHOLD 1024 * 10U // 10KB, "a few screenfuls" of data. +#define SHELL_SPECIAL (char_u *)"\t \"&'$;<>()\\|" + typedef struct { char *data; size_t cap, len; @@ -41,6 +47,498 @@ typedef struct { # include "os/shell.c.generated.h" #endif +static void save_patterns(int num_pat, char_u **pat, int *num_file, + char_u ***file) +{ + *file = xmalloc((size_t)num_pat * sizeof(char_u *)); + for (int i = 0; i < num_pat; i++) { + char_u *s = vim_strsave(pat[i]); + // Be compatible with expand_filename(): halve the number of + // backslashes. + backslash_halve(s); + (*file)[i] = s; + } + *num_file = num_pat; +} + +static bool have_wildcard(int num, char_u **file) +{ + for (int i = 0; i < num; i++) { + if (path_has_wildcard(file[i])) { + return true; + } + } + return false; +} + +static bool have_dollars(int num, char_u **file) +{ + for (int i = 0; i < num; i++) { + if (vim_strchr(file[i], '$') != NULL) { + return true; + } + } + return false; +} + +/// Performs wildcard pattern matching using the shell. +/// +/// @param num_pat is the number of input patterns. +/// @param pat is an array of pointers to input patterns. +/// @param[out] num_file is pointer to number of matched file names. +/// Set to the number of pointers in *file. +/// @param[out] file is pointer to array of pointers to matched file names. +/// Memory pointed to by the initial value of *file will +/// not be freed. +/// Set to NULL if FAIL is returned. Otherwise points to +/// allocated memory. +/// @param flags is a combination of EW_* flags used in +/// expand_wildcards(). +/// If matching fails but EW_NOTFOUND is set in flags or +/// there are no wildcards, the patterns from pat are +/// copied into *file. +/// +/// @returns OK for success or FAIL for error. +int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, + char_u ***file, int flags) + FUNC_ATTR_NONNULL_ARG(3) + FUNC_ATTR_NONNULL_ARG(4) +{ + int i; + size_t len; + char_u *p; + bool dir; + char_u *extra_shell_arg = NULL; + ShellOpts shellopts = kShellOptExpand | kShellOptSilent; + int j; + char_u *tempname; + char_u *command; + FILE *fd; + char_u *buffer; +#define STYLE_ECHO 0 // use "echo", the default +#define STYLE_GLOB 1 // use "glob", for csh +#define STYLE_VIMGLOB 2 // use "vimglob", for Posix sh +#define STYLE_PRINT 3 // use "print -N", for zsh +#define STYLE_BT 4 // `cmd` expansion, execute the pattern directly + int shell_style = STYLE_ECHO; + int check_spaces; + static bool did_find_nul = false; + bool ampersent = false; + // vimglob() function to define for Posix shell + static char *sh_vimglob_func = + "vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >"; + + bool is_fish_shell = +#if defined(UNIX) + STRNCMP(invocation_path_tail(p_sh, NULL), "fish", 4) == 0; +#else + false; +#endif + + *num_file = 0; // default: no files found + *file = NULL; + + // If there are no wildcards, just copy the names to allocated memory. + // Saves a lot of time, because we don't have to start a new shell. + if (!have_wildcard(num_pat, pat)) { + save_patterns(num_pat, pat, num_file, file); + return OK; + } + + // Don't allow any shell command in the sandbox. + if (sandbox != 0 && check_secure()) { + return FAIL; + } + + // Don't allow the use of backticks in secure and restricted mode. + if (secure || restricted) { + for (i = 0; i < num_pat; i++) { + if (vim_strchr(pat[i], '`') != NULL + && (check_restricted() || check_secure())) { + return FAIL; + } + } + } + + // get a name for the temp file + if ((tempname = vim_tempname()) == NULL) { + EMSG(_(e_notmp)); + return FAIL; + } + + // Let the shell expand the patterns and write the result into the temp + // file. + // STYLE_BT: NL separated + // If expanding `cmd` execute it directly. + // STYLE_GLOB: NUL separated + // If we use *csh, "glob" will work better than "echo". + // STYLE_PRINT: NL or NUL separated + // If we use *zsh, "print -N" will work better than "glob". + // STYLE_VIMGLOB: NL separated + // If we use *sh*, we define "vimglob()". + // STYLE_ECHO: space separated. + // A shell we don't know, stay safe and use "echo". + if (num_pat == 1 && *pat[0] == '`' + && (len = STRLEN(pat[0])) > 2 + && *(pat[0] + len - 1) == '`') { + shell_style = STYLE_BT; + } else if ((len = STRLEN(p_sh)) >= 3) { + if (STRCMP(p_sh + len - 3, "csh") == 0) { + shell_style = STYLE_GLOB; + } else if (STRCMP(p_sh + len - 3, "zsh") == 0) { + shell_style = STYLE_PRINT; + } + } + if (shell_style == STYLE_ECHO + && strstr((char *)path_tail(p_sh), "sh") != NULL) { + shell_style = STYLE_VIMGLOB; + } + + // Compute the length of the command. We need 2 extra bytes: for the + // optional '&' and for the NUL. + // Worst case: "unset nonomatch; print -N >" plus two is 29 + len = STRLEN(tempname) + 29; + if (shell_style == STYLE_VIMGLOB) { + len += STRLEN(sh_vimglob_func); + } + + for (i = 0; i < num_pat; i++) { + // Count the length of the patterns in the same way as they are put in + // "command" below. + len++; // add space + for (j = 0; pat[i][j] != NUL; j++) { + if (vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) { + len++; // may add a backslash + } + len++; + } + } + + if (is_fish_shell) { + len += sizeof("egin;"" end") - 1; + } + + command = xmalloc(len); + + // Build the shell command: + // - Set $nonomatch depending on EW_NOTFOUND (hopefully the shell + // recognizes this). + // - Add the shell command to print the expanded names. + // - Add the temp file name. + // - Add the file name patterns. + if (shell_style == STYLE_BT) { + // change `command; command& ` to (command; command ) + if (is_fish_shell) { + STRCPY(command, "begin; "); + } else { + STRCPY(command, "("); + } + STRCAT(command, pat[0] + 1); // exclude first backtick + p = command + STRLEN(command) - 1; + if (is_fish_shell) { + *p-- = ';'; + STRCAT(command, " end"); + } else { + *p-- = ')'; // remove last backtick + } + while (p > command && ascii_iswhite(*p)) { + p--; + } + if (*p == '&') { // remove trailing '&' + ampersent = true; + *p = ' '; + } + STRCAT(command, ">"); + } else { + if (flags & EW_NOTFOUND) { + STRCPY(command, "set nonomatch; "); + } else { + STRCPY(command, "unset nonomatch; "); + } + if (shell_style == STYLE_GLOB) { + STRCAT(command, "glob >"); + } else if (shell_style == STYLE_PRINT) { + STRCAT(command, "print -N >"); + } else if (shell_style == STYLE_VIMGLOB) { + STRCAT(command, sh_vimglob_func); + } else { + STRCAT(command, "echo >"); + } + } + + STRCAT(command, tempname); + + if (shell_style != STYLE_BT) { + for (i = 0; i < num_pat; i++) { + // Put a backslash before special + // characters, except inside ``. + bool intick = false; + + p = command + STRLEN(command); + *p++ = ' '; + for (j = 0; pat[i][j] != NUL; j++) { + if (pat[i][j] == '`') { + intick = !intick; + } else if (pat[i][j] == '\\' && pat[i][j + 1] != NUL) { + // Remove a backslash, take char literally. But keep + // backslash inside backticks, before a special character + // and before a backtick. + if (intick + || vim_strchr(SHELL_SPECIAL, pat[i][j + 1]) != NULL + || pat[i][j + 1] == '`') { + *p++ = '\\'; + } + j++; + } else if (!intick + && ((flags & EW_KEEPDOLLAR) == 0 || pat[i][j] != '$') + && vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) { + // Put a backslash before a special character, but not + // when inside ``. And not for $var when EW_KEEPDOLLAR is + // set. + *p++ = '\\'; + } + + // Copy one character. + *p++ = pat[i][j]; + } + *p = NUL; + } + } + + if (flags & EW_SILENT) { + shellopts |= kShellOptHideMess; + } + + if (ampersent) { + STRCAT(command, "&"); // put the '&' after the redirection + } + + // Using zsh -G: If a pattern has no matches, it is just deleted from + // the argument list, otherwise zsh gives an error message and doesn't + // expand any other pattern. + if (shell_style == STYLE_PRINT) { + extra_shell_arg = (char_u *)"-G"; // Use zsh NULL_GLOB option + + // If we use -f then shell variables set in .cshrc won't get expanded. + // vi can do it, so we will too, but it is only necessary if there is a "$" + // in one of the patterns, otherwise we can still use the fast option. + } else if (shell_style == STYLE_GLOB && !have_dollars(num_pat, pat)) { + extra_shell_arg = (char_u *)"-f"; // Use csh fast option + } + + // execute the shell command + i = call_shell(command, shellopts, extra_shell_arg); + + // When running in the background, give it some time to create the temp + // file, but don't wait for it to finish. + if (ampersent) { + os_delay(10L, true); + } + + xfree(command); + + if (i) { // os_call_shell() failed + os_remove((char *)tempname); + xfree(tempname); + // With interactive completion, the error message is not printed. + if (!(flags & EW_SILENT)) { + msg_putchar('\n'); // clear bottom line quickly + cmdline_row = Rows - 1; // continue on last line + MSG(_(e_wildexpand)); + msg_start(); // don't overwrite this message + } + + // If a `cmd` expansion failed, don't list `cmd` as a match, even when + // EW_NOTFOUND is given + if (shell_style == STYLE_BT) { + return FAIL; + } + goto notfound; + } + + // read the names from the file into memory + fd = fopen((char *)tempname, READBIN); + if (fd == NULL) { + // Something went wrong, perhaps a file name with a special char. + if (!(flags & EW_SILENT)) { + MSG(_(e_wildexpand)); + msg_start(); // don't overwrite this message + } + xfree(tempname); + goto notfound; + } + int fseek_res = fseek(fd, 0L, SEEK_END); + if (fseek_res < 0) { + xfree(tempname); + fclose(fd); + return FAIL; + } + int64_t templen = ftell(fd); // get size of temp file + if (templen < 0) { + xfree(tempname); + fclose(fd); + return FAIL; + } +#if SIZEOF_LONG_LONG > SIZEOF_SIZE_T + assert(templen <= (long long)SIZE_MAX); // NOLINT(runtime/int) +#endif + len = (size_t)templen; + fseek(fd, 0L, SEEK_SET); + buffer = xmalloc(len + 1); + // fread() doesn't terminate buffer with NUL; + // appropriate termination (not always NUL) is done below. + size_t readlen = fread((char *)buffer, 1, len, fd); + fclose(fd); + os_remove((char *)tempname); + if (readlen != len) { + // unexpected read error + EMSG2(_(e_notread), tempname); + xfree(tempname); + xfree(buffer); + return FAIL; + } + xfree(tempname); + + // file names are separated with Space + if (shell_style == STYLE_ECHO) { + buffer[len] = '\n'; // make sure the buffer ends in NL + p = buffer; + for (i = 0; *p != '\n'; i++) { // count number of entries + while (*p != ' ' && *p != '\n') { + p++; + } + p = skipwhite(p); // skip to next entry + } + // file names are separated with NL + } else if (shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) { + buffer[len] = NUL; // make sure the buffer ends in NUL + p = buffer; + for (i = 0; *p != NUL; i++) { // count number of entries + while (*p != '\n' && *p != NUL) { + p++; + } + if (*p != NUL) { + p++; + } + p = skipwhite(p); // skip leading white space + } + // file names are separated with NUL + } else { + // Some versions of zsh use spaces instead of NULs to separate + // results. Only do this when there is no NUL before the end of the + // buffer, otherwise we would never be able to use file names with + // embedded spaces when zsh does use NULs. + // When we found a NUL once, we know zsh is OK, set did_find_nul and + // don't check for spaces again. + check_spaces = false; + if (shell_style == STYLE_PRINT && !did_find_nul) { + // If there is a NUL, set did_find_nul, else set check_spaces + buffer[len] = NUL; + if (len && (int)STRLEN(buffer) < (int)len) { + did_find_nul = true; + } else { + check_spaces = true; + } + } + + // Make sure the buffer ends with a NUL. For STYLE_PRINT there + // already is one, for STYLE_GLOB it needs to be added. + if (len && buffer[len - 1] == NUL) { + len--; + } else { + buffer[len] = NUL; + } + for (p = buffer; p < buffer + len; p++) { + if (*p == NUL || (*p == ' ' && check_spaces)) { // count entry + i++; + *p = NUL; + } + } + if (len) { + i++; // count last entry + } + } + assert(buffer[len] == NUL || buffer[len] == '\n'); + + if (i == 0) { + // Can happen when using /bin/sh and typing ":e $NO_SUCH_VAR^I". + // /bin/sh will happily expand it to nothing rather than returning an + // error; and hey, it's good to check anyway -- webb. + xfree(buffer); + goto notfound; + } + *num_file = i; + *file = xmalloc(sizeof(char_u *) * (size_t)i); + + // Isolate the individual file names. + p = buffer; + for (i = 0; i < *num_file; i++) { + (*file)[i] = p; + // Space or NL separates + if (shell_style == STYLE_ECHO || shell_style == STYLE_BT + || shell_style == STYLE_VIMGLOB) { + while (!(shell_style == STYLE_ECHO && *p == ' ') + && *p != '\n' && *p != NUL) { + p++; + } + if (p == buffer + len) { // last entry + *p = NUL; + } else { + *p++ = NUL; + p = skipwhite(p); // skip to next entry + } + } else { // NUL separates + while (*p && p < buffer + len) { // skip entry + p++; + } + p++; // skip NUL + } + } + + // Move the file names to allocated memory. + for (j = 0, i = 0; i < *num_file; i++) { + // Require the files to exist. Helps when using /bin/sh + if (!(flags & EW_NOTFOUND) && !os_path_exists((*file)[i])) { + continue; + } + + // check if this entry should be included + dir = (os_isdir((*file)[i])); + if ((dir && !(flags & EW_DIR)) || (!dir && !(flags & EW_FILE))) { + continue; + } + + // Skip files that are not executable if we check for that. + if (!dir && (flags & EW_EXEC) + && !os_can_exe((char *)(*file)[i], NULL, !(flags & EW_SHELLCMD))) { + continue; + } + + p = xmalloc(STRLEN((*file)[i]) + 1 + dir); + STRCPY(p, (*file)[i]); + if (dir) { + add_pathsep((char *)p); // add '/' to a directory name + } + (*file)[j++] = p; + } + xfree(buffer); + *num_file = j; + + if (*num_file == 0) { // rejected all entries + XFREE_CLEAR(*file); + goto notfound; + } + + return OK; + +notfound: + if (flags & EW_NOTFOUND) { + save_patterns(num_pat, pat, num_file, file); + return OK; + } + return FAIL; +} + /// Builds the argument vector for running the user-configured 'shell' (p_sh) /// with an optional command prefixed by 'shellcmdflag' (p_shcf). E.g.: /// @@ -423,10 +921,11 @@ static bool out_data_decide_throttle(size_t size) pulse_msg[1] = (tick > 1) ? '.' : ' '; pulse_msg[2] = (tick > 2) ? '.' : ' '; if (visit == 1) { - msg_putchar('\n'); + msg_puts("...\n"); } msg_putchar('\r'); // put cursor at start of line msg_puts(pulse_msg); + msg_putchar('\r'); ui_flush(); return true; } diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index ba6226ef9d..bfe230b521 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -50,22 +50,13 @@ void signal_init(void) signal_watcher_init(&main_loop, &shup, NULL); signal_watcher_init(&main_loop, &squit, NULL); signal_watcher_init(&main_loop, &sterm, NULL); -#ifdef SIGPIPE - signal_watcher_start(&spipe, on_signal, SIGPIPE); -#endif - signal_watcher_start(&shup, on_signal, SIGHUP); -#ifdef SIGQUIT - signal_watcher_start(&squit, on_signal, SIGQUIT); -#endif - signal_watcher_start(&sterm, on_signal, SIGTERM); #ifdef SIGPWR signal_watcher_init(&main_loop, &spwr, NULL); - signal_watcher_start(&spwr, on_signal, SIGPWR); #endif #ifdef SIGUSR1 signal_watcher_init(&main_loop, &susr1, NULL); - signal_watcher_start(&susr1, on_signal, SIGUSR1); #endif + signal_start(); } void signal_teardown(void) @@ -83,11 +74,33 @@ void signal_teardown(void) #endif } +void signal_start(void) +{ +#ifdef SIGPIPE + signal_watcher_start(&spipe, on_signal, SIGPIPE); +#endif + signal_watcher_start(&shup, on_signal, SIGHUP); +#ifdef SIGQUIT + signal_watcher_start(&squit, on_signal, SIGQUIT); +#endif + signal_watcher_start(&sterm, on_signal, SIGTERM); +#ifdef SIGPWR + signal_watcher_start(&spwr, on_signal, SIGPWR); +#endif +#ifdef SIGUSR1 + signal_watcher_start(&susr1, on_signal, SIGUSR1); +#endif +} + void signal_stop(void) { +#ifdef SIGPIPE signal_watcher_stop(&spipe); +#endif signal_watcher_stop(&shup); +#ifdef SIGQUIT signal_watcher_stop(&squit); +#endif signal_watcher_stop(&sterm); #ifdef SIGPWR signal_watcher_stop(&spwr); @@ -144,6 +157,7 @@ static void deadly_signal(int signum) { // Set the v:dying variable. set_vim_var_nr(VV_DYING, 1); + v_dying = 1; WLOG("got signal %d (%s)", signum, signal_name(signum)); diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index 4dd0614fe2..346e40e02e 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -2,9 +2,6 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> -#include <stdint.h> -#include <stdbool.h> -#include <time.h> #include <limits.h> #include <uv.h> @@ -13,7 +10,7 @@ #include "nvim/os/time.h" #include "nvim/os/input.h" #include "nvim/event/loop.h" -#include "nvim/vim.h" +#include "nvim/os/os.h" #include "nvim/main.h" static uv_mutex_t delay_mutex; @@ -112,6 +109,10 @@ void os_microdelay(uint64_t us, bool ignoreinput) uv_mutex_unlock(&delay_mutex); } +// Cache of the current timezone name as retrieved from TZ, or an empty string +// where unset, up to 64 octets long including trailing null byte. +static char tz_cache[64]; + /// Portable version of POSIX localtime_r() /// /// @return NULL in case of error @@ -120,6 +121,19 @@ struct tm *os_localtime_r(const time_t *restrict clock, { #ifdef UNIX // POSIX provides localtime_r() as a thread-safe version of localtime(). + // + // Check to see if the environment variable TZ has changed since the last run. + // Call tzset(3) to update the global timezone variables if it has. + // POSIX standard doesn't require localtime_r() implementations to do that + // as it does with localtime(), and we don't want to call tzset() every time. + const char *tz = os_getenv("TZ"); + if (tz == NULL) { + tz = ""; + } + if (strncmp(tz_cache, tz, sizeof(tz_cache) - 1) != 0) { + tzset(); + xstrlcpy(tz_cache, tz, sizeof(tz_cache)); + } return localtime_r(clock, result); // NOLINT(runtime/threadsafe_fn) #else // Windows version of localtime() is thread-safe. @@ -144,6 +158,39 @@ struct tm *os_localtime(struct tm *result) FUNC_ATTR_NONNULL_ALL return os_localtime_r(&rawtime, result); } +/// Portable version of POSIX ctime_r() +/// +/// @param clock[in] +/// @param result[out] Pointer to a 'char' where the result should be placed +/// @param result_len length of result buffer +/// @return human-readable string of current local time +char *os_ctime_r(const time_t *restrict clock, char *restrict result, + size_t result_len) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET +{ + struct tm clock_local; + struct tm *clock_local_ptr = os_localtime_r(clock, &clock_local); + // MSVC returns NULL for an invalid value of seconds. + if (clock_local_ptr == NULL) { + snprintf(result, result_len, "%s\n", _("(Invalid)")); + } else { + strftime(result, result_len, "%a %b %d %H:%M:%S %Y\n", clock_local_ptr); + } + return result; +} + +/// Gets the current Unix timestamp and adjusts it to local time. +/// +/// @param result[out] Pointer to a 'char' where the result should be placed +/// @param result_len length of result buffer +/// @return human-readable string of current local time +char *os_ctime(char *result, size_t result_len) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET +{ + time_t rawtime = time(NULL); + return os_ctime_r(&rawtime, result, result_len); +} + /// Obtains the current Unix timestamp. /// /// @return Seconds since epoch. diff --git a/src/nvim/os/tty.c b/src/nvim/os/tty.c index bd5b9b4506..c80ef99084 100644 --- a/src/nvim/os/tty.c +++ b/src/nvim/os/tty.c @@ -6,6 +6,7 @@ // #include "nvim/os/os.h" +#include "nvim/os/tty.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/tty.c.generated.h" @@ -27,7 +28,7 @@ void os_tty_guess_term(const char **term, int out_fd) if (winpty) { // Force TERM=win32con when running in winpty. *term = "win32con"; - uv_set_vterm_state(UV_UNSUPPORTED); + uv_tty_set_vterm_state(UV_TTY_UNSUPPORTED); return; } @@ -54,7 +55,7 @@ void os_tty_guess_term(const char **term, int out_fd) } if (conemu_ansi) { - uv_set_vterm_state(UV_SUPPORTED); + uv_tty_set_vterm_state(UV_TTY_SUPPORTED); } } #endif diff --git a/src/nvim/os/users.c b/src/nvim/os/users.c index c6463c2c92..9374693550 100644 --- a/src/nvim/os/users.c +++ b/src/nvim/os/users.c @@ -13,6 +13,25 @@ #ifdef HAVE_PWD_H # include <pwd.h> #endif +#ifdef WIN32 +# include <lm.h> +#endif + +// Add a user name to the list of users in garray_T *users. +// Do nothing if user name is NULL or empty. +static void add_user(garray_T *users, char *user, bool need_copy) +{ + char *user_copy = (user != NULL && need_copy) + ? xstrdup(user) : user; + + if (user_copy == NULL || *user_copy == NUL) { + if (need_copy) { + xfree(user); + } + return; + } + GA_APPEND(char *, users, user_copy); +} // Initialize users garray and fill it with os usernames. // Return Ok for success, FAIL for failure. @@ -24,16 +43,66 @@ int os_get_usernames(garray_T *users) ga_init(users, sizeof(char *), 20); # if defined(HAVE_GETPWENT) && defined(HAVE_PWD_H) - struct passwd *pw; + { + struct passwd *pw; + + setpwent(); + while ((pw = getpwent()) != NULL) { + add_user(users, pw->pw_name, true); + } + endpwent(); + } +# elif defined(WIN32) + { + DWORD nusers = 0, ntotal = 0, i; + PUSER_INFO_0 uinfo; + + if (NetUserEnum(NULL, 0, 0, (LPBYTE *)&uinfo, MAX_PREFERRED_LENGTH, + &nusers, &ntotal, NULL) == NERR_Success) { + for (i = 0; i < nusers; i++) { + char *user; + int conversion_result = utf16_to_utf8(uinfo[i].usri0_name, -1, &user); + if (conversion_result != 0) { + EMSG2("utf16_to_utf8 failed: %d", conversion_result); + break; + } + add_user(users, user, false); + } + + NetApiBufferFree(uinfo); + } + } +# endif +# if defined(HAVE_GETPWNAM) + { + const char *user_env = os_getenv("USER"); + + // The $USER environment variable may be a valid remote user name (NIS, + // LDAP) not already listed by getpwent(), as getpwent() only lists + // local user names. If $USER is not already listed, check whether it + // is a valid remote user name using getpwnam() and if it is, add it to + // the list of user names. + + if (user_env != NULL && *user_env != NUL) { + int i; + + for (i = 0; i < users->ga_len; i++) { + char *local_user = ((char **)users->ga_data)[i]; + + if (STRCMP(local_user, user_env) == 0) { + break; + } + } + + if (i == users->ga_len) { + struct passwd *pw = getpwnam(user_env); // NOLINT - setpwent(); - while ((pw = getpwent()) != NULL) { - // pw->pw_name shouldn't be NULL but just in case... - if (pw->pw_name != NULL) { - GA_APPEND(char *, users, xstrdup(pw->pw_name)); + if (pw != NULL) { + add_user(users, pw->pw_name, true); + } + } } } - endpwent(); # endif return OK; |