aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/os
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/os')
-rw-r--r--src/nvim/os/env.c207
-rw-r--r--src/nvim/os/fs.c69
-rw-r--r--src/nvim/os/input.c9
-rw-r--r--src/nvim/os/os_win_console.c74
-rw-r--r--src/nvim/os/os_win_console.h12
-rw-r--r--src/nvim/os/pty_conpty_win.c199
-rw-r--r--src/nvim/os/pty_conpty_win.h22
-rw-r--r--src/nvim/os/pty_process_win.c136
-rw-r--r--src/nvim/os/pty_process_win.h15
-rw-r--r--src/nvim/os/shell.c501
-rw-r--r--src/nvim/os/signal.c34
-rw-r--r--src/nvim/os/time.c55
-rw-r--r--src/nvim/os/tty.c5
-rw-r--r--src/nvim/os/users.c83
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;