aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/os
diff options
context:
space:
mode:
authorJames McCoy <jamessan@jamessan.com>2018-03-28 21:52:06 -0400
committerJames McCoy <jamessan@jamessan.com>2018-03-28 21:54:39 -0400
commit79f9c2d9c650ceab27cdc6707fd6d7fa1de29fc1 (patch)
tree4e0589d75801f3ff6a9678f84f5009102766661e /src/nvim/os
parent4403864da3c48412595d439f36458d1e6ccfc49f (diff)
parent3f3de9b1a95d273463a87516365510dbffcaf3d2 (diff)
downloadrneovim-79f9c2d9c650ceab27cdc6707fd6d7fa1de29fc1.tar.gz
rneovim-79f9c2d9c650ceab27cdc6707fd6d7fa1de29fc1.tar.bz2
rneovim-79f9c2d9c650ceab27cdc6707fd6d7fa1de29fc1.zip
Merge branch 'master' into yagebu/option-fixes
Diffstat (limited to 'src/nvim/os')
-rw-r--r--src/nvim/os/env.c4
-rw-r--r--src/nvim/os/fileio.c88
-rw-r--r--src/nvim/os/fs.c119
-rw-r--r--src/nvim/os/input.c4
-rw-r--r--src/nvim/os/lang.c43
-rw-r--r--src/nvim/os/lang.h7
-rw-r--r--src/nvim/os/process.c253
-rw-r--r--src/nvim/os/process.h11
-rw-r--r--src/nvim/os/pty_process_unix.c45
-rw-r--r--src/nvim/os/pty_process_win.c413
-rw-r--r--src/nvim/os/pty_process_win.h28
-rw-r--r--src/nvim/os/shell.c143
-rw-r--r--src/nvim/os/signal.c3
-rw-r--r--src/nvim/os/time.c22
-rw-r--r--src/nvim/os/unix_defs.h3
-rw-r--r--src/nvim/os/win_defs.h20
16 files changed, 1016 insertions, 190 deletions
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index de0cd10d9c..3fcb9415c7 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -886,8 +886,8 @@ bool os_setenv_append_path(const char *fname)
// No prescribed maximum on unix.
# define MAX_ENVPATHLEN INT_MAX
#endif
- if (!path_is_absolute_path((char_u *)fname)) {
- EMSG2(_(e_intern2), "os_setenv_append_path()");
+ if (!path_is_absolute((char_u *)fname)) {
+ internal_error("os_setenv_append_path()");
return false;
}
const char *tail = (char *)path_tail_with_sep((char_u *)fname);
diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c
index 4309ac723c..a95adc86b6 100644
--- a/src/nvim/os/fileio.c
+++ b/src/nvim/os/fileio.c
@@ -4,7 +4,7 @@
/// @file fileio.c
///
/// Buffered reading/writing to a file. Unlike fileio.c this is not dealing with
-/// Neovim stuctures for buffer, with autocommands, etc: just fopen/fread/fwrite
+/// Nvim stuctures for buffer, with autocommands, etc: just fopen/fread/fwrite
/// replacement.
#include <assert.h>
@@ -26,6 +26,7 @@
#include "nvim/globals.h"
#include "nvim/rbuffer.h"
#include "nvim/macros.h"
+#include "nvim/message.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/fileio.c.generated.h"
@@ -38,17 +39,16 @@
/// @param[in] fname File name to open.
/// @param[in] flags Flags, @see FileOpenFlags. Currently reading from and
/// writing to the file at once is not supported, so either
-/// FILE_WRITE_ONLY or FILE_READ_ONLY is required.
+/// kFileWriteOnly or kFileReadOnly is required.
/// @param[in] mode Permissions for the newly created file (ignored if flags
-/// does not have FILE_CREATE\*).
+/// does not have kFileCreate\*).
///
-/// @return Error code (@see os_strerror()) or 0.
+/// @return Error code, or 0 on success. @see os_strerror()
int file_open(FileDescriptor *const ret_fp, const char *const fname,
const int flags, const int mode)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
int os_open_flags = 0;
- int fd;
TriState wr = kNone;
// -V:FLAG:501
#define FLAG(flags, flag, fcntl_flags, wrval, cond) \
@@ -73,14 +73,35 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname,
FLAG(flags, kFileNoSymlink, O_NOFOLLOW, kNone, true);
#endif
#undef FLAG
+ // wr is used for kFileReadOnly flag, but on
+ // QB:neovim-qb-slave-ubuntu-12-04-64bit it still errors out with
+ // `error: variable ‘wr’ set but not used [-Werror=unused-but-set-variable]`
+ (void)wr;
- fd = os_open(fname, os_open_flags, mode);
+ const int fd = os_open(fname, os_open_flags, mode);
if (fd < 0) {
return fd;
}
+ return file_open_fd(ret_fp, fd, (wr == kTrue));
+}
- ret_fp->wr = (wr == kTrue);
+/// Wrap file descriptor with FileDescriptor structure
+///
+/// @warning File descriptor wrapped like this must not be accessed by other
+/// means.
+///
+/// @param[out] ret_fp Address where information needed for reading from or
+/// writing to a file is saved
+/// @param[in] fd File descriptor to wrap.
+/// @param[in] wr True if fd is opened for writing only, false if it is read
+/// only.
+///
+/// @return Error code (@see os_strerror()) or 0. Currently always returns 0.
+int file_open_fd(FileDescriptor *const ret_fp, const int fd, const bool wr)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ ret_fp->wr = wr;
ret_fp->fd = fd;
ret_fp->eof = false;
ret_fp->rv = rbuffer_new(kRWBufferSize);
@@ -94,12 +115,11 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname,
/// Like file_open(), but allocate and return ret_fp
///
-/// @param[out] error Error code, @see os_strerror(). Is set to zero on
-/// success.
+/// @param[out] error Error code, or 0 on success. @see os_strerror()
/// @param[in] fname File name to open.
/// @param[in] flags Flags, @see FileOpenFlags.
/// @param[in] mode Permissions for the newly created file (ignored if flags
-/// does not have FILE_CREATE\*).
+/// does not have kFileCreate\*).
///
/// @return [allocated] Opened file or NULL in case of error.
FileDescriptor *file_open_new(int *const error, const char *const fname,
@@ -114,6 +134,25 @@ FileDescriptor *file_open_new(int *const error, const char *const fname,
return fp;
}
+/// Like file_open_fd(), but allocate and return ret_fp
+///
+/// @param[out] error Error code, or 0 on success. @see os_strerror()
+/// @param[in] fd File descriptor to wrap.
+/// @param[in] wr True if fd is opened for writing only, false if it is read
+/// only.
+///
+/// @return [allocated] Opened file or NULL in case of error.
+FileDescriptor *file_open_fd_new(int *const error, const int fd, const bool wr)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ FileDescriptor *const fp = xmalloc(sizeof(*fp));
+ if ((*error = file_open_fd(fp, fd, wr)) != 0) {
+ xfree(fp);
+ return NULL;
+ }
+ return fp;
+}
+
/// Close file and free its buffer
///
/// @param[in,out] fp File to close.
@@ -345,3 +384,32 @@ ptrdiff_t file_skip(FileDescriptor *const fp, const size_t size)
return (ptrdiff_t)read_bytes;
}
+
+/// Msgpack callback for writing to a file
+///
+/// @param data File to write to.
+/// @param[in] buf Data to write.
+/// @param[in] len Length of the data to write.
+///
+/// @return 0 in case of success, -1 in case of error.
+int msgpack_file_write(void *data, const char *buf, size_t len)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ assert(len < PTRDIFF_MAX);
+ const ptrdiff_t written_bytes = file_write((FileDescriptor *)data, buf, len);
+ if (written_bytes < 0) {
+ return msgpack_file_write_error((int)written_bytes);
+ }
+ return 0;
+}
+
+/// Print error which occurs when failing to write msgpack data
+///
+/// @param[in] error Error code of the error to print.
+///
+/// @return -1 (error return for msgpack_packer callbacks).
+int msgpack_file_write_error(const int error)
+{
+ emsgf(_("E5420: Failed to write to file: %s"), os_strerror(error));
+ return -1;
+}
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index 14dacd97c4..b7c2714296 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -131,23 +131,14 @@ bool os_isdir(const char_u *name)
/// NODE_WRITABLE: writable device, socket, fifo, etc.
/// NODE_OTHER: non-writable things
int os_nodetype(const char *name)
+ FUNC_ATTR_NONNULL_ALL
{
-#ifdef WIN32
- // Edge case from Vim os_win32.c:
- // We can't open a file with a name "\\.\con" or "\\.\prn", trying to read
- // from it later will cause Vim to hang. Thus return NODE_WRITABLE here.
- if (STRNCMP(name, "\\\\.\\", 4) == 0) {
- return NODE_WRITABLE;
- }
-#endif
-
+#ifndef WIN32 // Unix
uv_stat_t statbuf;
if (0 != os_stat(name, &statbuf)) {
return NODE_NORMAL; // File doesn't exist.
}
-
-#ifndef WIN32
- // libuv does not handle BLK and DIR in uv_handle_type.
+ // uv_handle_type does not distinguish BLK and DIR.
// Related: https://github.com/joyent/libuv/pull/1421
if (S_ISREG(statbuf.st_mode) || S_ISDIR(statbuf.st_mode)) {
return NODE_NORMAL;
@@ -155,48 +146,51 @@ int os_nodetype(const char *name)
if (S_ISBLK(statbuf.st_mode)) { // block device isn't writable
return NODE_OTHER;
}
-#endif
+ // Everything else is writable?
+ // buf_write() expects NODE_WRITABLE for char device /dev/stderr.
+ return NODE_WRITABLE;
+#else // Windows
+ // Edge case from Vim os_win32.c:
+ // We can't open a file with a name "\\.\con" or "\\.\prn", trying to read
+ // from it later will cause Vim to hang. Thus return NODE_WRITABLE here.
+ if (STRNCMP(name, "\\\\.\\", 4) == 0) {
+ return NODE_WRITABLE;
+ }
- // Vim os_win32.c:mch_nodetype does this (since patch 7.4.015):
- // if (enc_codepage >= 0 && (int)GetACP() != enc_codepage) {
- // wn = enc_to_utf16(name, NULL);
- // hFile = CreatFile(wn, ...)
- // to get a HANDLE. But libuv just calls win32's _get_osfhandle() on the fd we
- // give it. uv_fs_open calls fs__capture_path which does a similar dance and
- // saves us the hassle.
+ // Vim os_win32.c:mch_nodetype does (since 7.4.015):
+ // wn = enc_to_utf16(name, NULL);
+ // hFile = CreatFile(wn, ...)
+ // to get a HANDLE. Whereas libuv just calls _get_osfhandle() on the fd we
+ // give it. But uv_fs_open later calls fs__capture_path which does a similar
+ // utf8-to-utf16 dance and saves us the hassle.
- int nodetype = NODE_WRITABLE;
+ // macOS: os_open(/dev/stderr) would return UV_EACCES.
int fd = os_open(name, O_RDONLY
-#ifdef O_NONBLOCK
+# ifdef O_NONBLOCK
| O_NONBLOCK
-#endif
+# endif
, 0);
- if (fd == -1) {
- return NODE_OTHER; // open() failed.
+ if (fd < 0) { // open() failed.
+ return NODE_NORMAL;
+ }
+ int guess = uv_guess_handle(fd);
+ if (close(fd) == -1) {
+ ELOG("close(%d) failed. name='%s'", fd, name);
}
- switch (uv_guess_handle(fd)) {
- case UV_TTY: // FILE_TYPE_CHAR
- nodetype = NODE_WRITABLE;
- break;
- case UV_FILE: // FILE_TYPE_DISK
- nodetype = NODE_NORMAL;
- break;
- case UV_NAMED_PIPE: // not handled explicitly in Vim os_win32.c
- case UV_UDP: // unix only
- case UV_TCP: // unix only
+ switch (guess) {
+ case UV_TTY: // FILE_TYPE_CHAR
+ return NODE_WRITABLE;
+ case UV_FILE: // FILE_TYPE_DISK
+ return NODE_NORMAL;
+ case UV_NAMED_PIPE: // not handled explicitly in Vim os_win32.c
+ case UV_UDP: // unix only
+ case UV_TCP: // unix only
case UV_UNKNOWN_HANDLE:
default:
-#ifdef WIN32
- nodetype = NODE_NORMAL;
-#else
- nodetype = NODE_WRITABLE; // Everything else is writable?
-#endif
- break;
+ return NODE_OTHER; // Vim os_win32.c default
}
-
- close(fd);
- return nodetype;
+#endif
}
/// Gets the absolute path of the currently running executable.
@@ -228,7 +222,7 @@ int os_exepath(char *buffer, size_t *size)
bool os_can_exe(const char_u *name, char_u **abspath, bool use_path)
FUNC_ATTR_NONNULL_ARG(1)
{
- bool no_path = !use_path || path_is_absolute_path(name);
+ bool no_path = !use_path || path_is_absolute(name);
#ifndef WIN32
// If the filename is "qualified" (relative or absolute) do not check $PATH.
no_path |= (name[0] == '.'
@@ -250,7 +244,7 @@ bool os_can_exe(const char_u *name, char_u **abspath, bool use_path)
#endif
if (ok) {
if (abspath != NULL) {
- *abspath = save_absolute_path(name);
+ *abspath = save_abs_path(name);
}
return true;
}
@@ -363,7 +357,7 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath)
#endif
if (ok) {
if (abspath != NULL) { // Caller asked for a copy of the path.
- *abspath = save_absolute_path((char_u *)buf);
+ *abspath = save_abs_path((char_u *)buf);
}
rv = true;
@@ -393,9 +387,11 @@ end:
/// @param mode Permissions for the newly-created file (IGNORED if 'flags' is
/// not `O_CREAT` or `O_TMPFILE`), subject to the current umask
/// @return file descriptor, or libuv error code on failure
-int os_open(const char* path, int flags, int mode)
- FUNC_ATTR_NONNULL_ALL
+int os_open(const char *path, int flags, int mode)
{
+ if (path == NULL) { // uv_fs_open asserts on NULL. #7561
+ return UV_EINVAL;
+ }
int r;
RUN_UV_FS_FUNC(r, uv_fs_open, path, flags, mode, NULL);
return r;
@@ -465,7 +461,7 @@ ptrdiff_t os_read(const int fd, bool *ret_eof, char *const ret_buf,
while (read_bytes != size) {
assert(size >= read_bytes);
const ptrdiff_t cur_read_bytes = read(fd, ret_buf + read_bytes,
- size - read_bytes);
+ IO_COUNT(size - read_bytes));
if (cur_read_bytes > 0) {
read_bytes += (size_t)cur_read_bytes;
}
@@ -568,7 +564,7 @@ ptrdiff_t os_write(const int fd, const char *const buf, const size_t size)
while (written_bytes != size) {
assert(size >= written_bytes);
const ptrdiff_t cur_written_bytes = write(fd, buf + written_bytes,
- size - written_bytes);
+ IO_COUNT(size - written_bytes));
if (cur_written_bytes > 0) {
written_bytes += (size_t)cur_written_bytes;
}
@@ -602,10 +598,13 @@ int os_fsync(int fd)
/// Get stat information for a file.
///
-/// @return libuv return code.
+/// @return libuv return code, or -errno
static int os_stat(const char *name, uv_stat_t *statbuf)
- FUNC_ATTR_NONNULL_ALL
+ FUNC_ATTR_NONNULL_ARG(2)
{
+ if (!name) {
+ return UV_EINVAL;
+ }
uv_fs_t request;
int result = uv_fs_stat(&fs_loop, &request, name, NULL);
*statbuf = request.statbuf;
@@ -617,7 +616,6 @@ static int os_stat(const char *name, uv_stat_t *statbuf)
///
/// @return libuv error code on error.
int32_t os_getperm(const char *name)
- FUNC_ATTR_NONNULL_ALL
{
uv_stat_t statbuf;
int stat_result = os_stat(name, &statbuf);
@@ -656,7 +654,6 @@ int os_fchown(int fd, uv_uid_t owner, uv_gid_t group)
///
/// @return `true` if `path` exists
bool os_path_exists(const char_u *path)
- FUNC_ATTR_NONNULL_ALL
{
uv_stat_t statbuf;
return os_stat((char *)path, &statbuf) == kLibuvSuccess;
@@ -846,7 +843,7 @@ int os_remove(const char *path)
/// @param[out] file_info Pointer to a FileInfo to put the information in.
/// @return `true` on success, `false` for failure.
bool os_fileinfo(const char *path, FileInfo *file_info)
- FUNC_ATTR_NONNULL_ALL
+ FUNC_ATTR_NONNULL_ARG(2)
{
return os_stat(path, &(file_info->stat)) == kLibuvSuccess;
}
@@ -857,8 +854,11 @@ bool os_fileinfo(const char *path, FileInfo *file_info)
/// @param[out] file_info Pointer to a FileInfo to put the information in.
/// @return `true` on success, `false` for failure.
bool os_fileinfo_link(const char *path, FileInfo *file_info)
- FUNC_ATTR_NONNULL_ALL
+ FUNC_ATTR_NONNULL_ARG(2)
{
+ if (path == NULL) {
+ return false;
+ }
uv_fs_t request;
int result = uv_fs_lstat(&fs_loop, &request, path, NULL);
file_info->stat = request.statbuf;
@@ -990,7 +990,7 @@ bool os_fileid_equal_fileinfo(const FileID *file_id,
/// to and return that name in allocated memory.
/// Otherwise NULL is returned.
char *os_resolve_shortcut(const char *fname)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC
{
HRESULT hr;
IPersistFile *ppf = NULL;
@@ -1073,7 +1073,8 @@ shortcut_end:
#endif
-int os_translate_sys_error(int sys_errno) {
+int os_translate_sys_error(int sys_errno)
+{
#ifdef HAVE_UV_TRANSLATE_SYS_ERROR
return uv_translate_sys_error(sys_errno);
#elif defined(WIN32)
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index 31e06ce404..405500767d 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -37,7 +37,7 @@ typedef enum {
static Stream read_stream = {.closed = true};
static RBuffer *input_buffer = NULL;
static bool input_eof = false;
-static int global_fd = 0;
+static int global_fd = -1;
static int events_enabled = 0;
static bool blocking = false;
@@ -188,7 +188,7 @@ size_t input_enqueue(String keys)
uint8_t buf[6] = { 0 };
unsigned int new_size
= trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, true,
- true);
+ false);
if (new_size) {
new_size = handle_mouse_event(&ptr, buf, new_size);
diff --git a/src/nvim/os/lang.c b/src/nvim/os/lang.c
new file mode 100644
index 0000000000..47c278ee97
--- /dev/null
+++ b/src/nvim/os/lang.c
@@ -0,0 +1,43 @@
+// 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
+
+#ifdef __APPLE__
+# define Boolean CFBoolean // Avoid conflict with API's Boolean
+# include <CoreFoundation/CFLocale.h>
+# include <CoreFoundation/CFString.h>
+# undef Boolean
+#endif
+
+#ifdef HAVE_LOCALE_H
+# include <locale.h>
+#endif
+#include "nvim/os/os.h"
+
+void lang_init(void)
+{
+#ifdef __APPLE__
+ if (os_getenv("LANG") == NULL) {
+ CFLocaleRef cf_locale = CFLocaleCopyCurrent();
+ CFTypeRef cf_lang_region = CFLocaleGetValue(cf_locale,
+ kCFLocaleIdentifier);
+ CFRetain(cf_lang_region);
+ CFRelease(cf_locale);
+
+ const char *lang_region = CFStringGetCStringPtr(cf_lang_region,
+ kCFStringEncodingUTF8);
+ if (lang_region) {
+ os_setenv("LANG", lang_region, true);
+ } else {
+ char buf[20] = { 0 };
+ if (CFStringGetCString(cf_lang_region, buf, 20,
+ kCFStringEncodingUTF8)) {
+ os_setenv("LANG", lang_region, true);
+ }
+ }
+ CFRelease(cf_lang_region);
+# ifdef HAVE_LOCALE_H
+ setlocale(LC_ALL, "");
+# endif
+ }
+#endif
+}
diff --git a/src/nvim/os/lang.h b/src/nvim/os/lang.h
new file mode 100644
index 0000000000..f60e064f57
--- /dev/null
+++ b/src/nvim/os/lang.h
@@ -0,0 +1,7 @@
+#ifndef NVIM_OS_LANG_H
+#define NVIM_OS_LANG_H
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/lang.h.generated.h"
+#endif
+#endif // NVIM_OS_LANG_H
diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c
new file mode 100644
index 0000000000..e23ba8a4ee
--- /dev/null
+++ b/src/nvim/os/process.c
@@ -0,0 +1,253 @@
+// 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
+
+/// OS process functions
+///
+/// psutil is a good reference for cross-platform syscall voodoo:
+/// https://github.com/giampaolo/psutil/tree/master/psutil/arch
+
+#include <uv.h> // for HANDLE (win32)
+
+#ifdef WIN32
+# include <tlhelp32.h> // for CreateToolhelp32Snapshot
+#endif
+
+#if defined(__FreeBSD__) // XXX: OpenBSD, NetBSD ?
+# include <string.h>
+# include <sys/types.h>
+# include <sys/user.h>
+#endif
+
+#if defined(__APPLE__) || defined(BSD)
+# include <sys/sysctl.h>
+# include <pwd.h>
+#endif
+
+#include "nvim/globals.h"
+#include "nvim/log.h"
+#include "nvim/os/process.h"
+#include "nvim/os/os.h"
+#include "nvim/os/os_defs.h"
+#include "nvim/api/private/helpers.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/process.c.generated.h"
+#endif
+
+#ifdef WIN32
+static bool os_proc_tree_kill_rec(HANDLE process, int sig)
+{
+ if (process == NULL) {
+ return false;
+ }
+ PROCESSENTRY32 pe;
+ DWORD pid = GetProcessId(process);
+
+ if (pid != 0) {
+ HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (h != INVALID_HANDLE_VALUE) {
+ pe.dwSize = sizeof(PROCESSENTRY32);
+ if (!Process32First(h, &pe)) {
+ goto theend;
+ }
+ do {
+ if (pe.th32ParentProcessID == pid) {
+ HANDLE ph = OpenProcess(PROCESS_ALL_ACCESS, false, pe.th32ProcessID);
+ if (ph != NULL) {
+ os_proc_tree_kill_rec(ph, sig);
+ CloseHandle(ph);
+ }
+ }
+ } while (Process32Next(h, &pe));
+ CloseHandle(h);
+ }
+ }
+
+theend:
+ return (bool)TerminateProcess(process, (unsigned int)sig);
+}
+/// Kills process `pid` and its descendants recursively.
+bool os_proc_tree_kill(int pid, int sig)
+{
+ assert(sig >= 0);
+ assert(sig == SIGTERM || sig == SIGKILL);
+ if (pid > 0) {
+ ILOG("terminating process tree: %d", pid);
+ HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, false, (DWORD)pid);
+ return os_proc_tree_kill_rec(h, sig);
+ } else {
+ ELOG("invalid pid: %d", pid);
+ }
+ return false;
+}
+#else
+/// Kills process group where `pid` is the process group leader.
+bool os_proc_tree_kill(int pid, int sig)
+{
+ assert(sig == SIGTERM || sig == SIGKILL);
+ int pgid = getpgid(pid);
+ if (pgid > 0) { // Ignore error. Never kill self (pid=0).
+ if (pgid == pid) {
+ ILOG("sending %s to process group: -%d",
+ sig == SIGTERM ? "SIGTERM" : "SIGKILL", pgid);
+ int rv = uv_kill(-pgid, sig);
+ return rv == 0;
+ } else {
+ // Should never happen, because process_spawn() did setsid() in the child.
+ ELOG("pgid %d != pid %d", pgid, pid);
+ }
+ } else {
+ ELOG("getpgid(%d) returned %d", pid, pgid);
+ }
+ return false;
+}
+#endif
+
+/// Gets the process ids of the immediate children of process `ppid`.
+///
+/// @param ppid Process to inspect.
+/// @param[out,allocated] proc_list Child process ids.
+/// @param[out] proc_count Number of child processes.
+/// @return 0 on success, 1 if process not found, 2 on other error.
+int os_proc_children(int ppid, int **proc_list, size_t *proc_count)
+{
+ if (ppid < 0) {
+ return 2;
+ }
+
+ int *temp = NULL;
+ *proc_list = NULL;
+ *proc_count = 0;
+
+#ifdef WIN32
+ PROCESSENTRY32 pe;
+
+ // Snapshot of all processes.
+ HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (h == INVALID_HANDLE_VALUE) {
+ return 2;
+ }
+
+ pe.dwSize = sizeof(PROCESSENTRY32);
+ // Get root process.
+ if (!Process32First(h, &pe)) {
+ CloseHandle(h);
+ return 2;
+ }
+ // Collect processes whose parent matches `ppid`.
+ do {
+ if (pe.th32ParentProcessID == (DWORD)ppid) {
+ temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp));
+ temp[*proc_count] = (int)pe.th32ProcessID;
+ (*proc_count)++;
+ }
+ } while (Process32Next(h, &pe));
+ CloseHandle(h);
+
+#elif defined(__APPLE__) || defined(BSD)
+# if defined(__APPLE__)
+# define KP_PID(o) o.kp_proc.p_pid
+# define KP_PPID(o) o.kp_eproc.e_ppid
+# elif defined(__FreeBSD__)
+# define KP_PID(o) o.ki_pid
+# define KP_PPID(o) o.ki_ppid
+# else
+# define KP_PID(o) o.p_pid
+# define KP_PPID(o) o.p_ppid
+# endif
+ static int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
+
+ // Get total process count.
+ size_t len = 0;
+ int rv = sysctl(name, ARRAY_SIZE(name) - 1, NULL, &len, NULL, 0);
+ if (rv) {
+ return 2;
+ }
+
+ // Get ALL processes.
+ struct kinfo_proc *p_list = xmalloc(len);
+ rv = sysctl(name, ARRAY_SIZE(name) - 1, p_list, &len, NULL, 0);
+ if (rv) {
+ xfree(p_list);
+ return 2;
+ }
+
+ // Collect processes whose parent matches `ppid`.
+ bool exists = false;
+ size_t p_count = len / sizeof(*p_list);
+ for (size_t i = 0; i < p_count; i++) {
+ exists = exists || KP_PID(p_list[i]) == ppid;
+ if (KP_PPID(p_list[i]) == ppid) {
+ temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp));
+ temp[*proc_count] = KP_PID(p_list[i]);
+ (*proc_count)++;
+ }
+ }
+ xfree(p_list);
+ if (!exists) {
+ return 1; // Process not found.
+ }
+
+#elif defined(__linux__)
+ char proc_p[256] = { 0 };
+ // Collect processes whose parent matches `ppid`.
+ // Rationale: children are defined in thread with same ID of process.
+ snprintf(proc_p, sizeof(proc_p), "/proc/%d/task/%d/children", ppid, ppid);
+ FILE *fp = fopen(proc_p, "r");
+ if (fp == NULL) {
+ return 2; // Process not found, or /proc/…/children not supported.
+ }
+ int match_pid;
+ while (fscanf(fp, "%d", &match_pid) > 0) {
+ temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp));
+ temp[*proc_count] = match_pid;
+ (*proc_count)++;
+ }
+ fclose(fp);
+#endif
+
+ *proc_list = temp;
+ return 0;
+}
+
+#ifdef WIN32
+/// Gets various properties of the process identified by `pid`.
+///
+/// @param pid Process to inspect.
+/// @return Map of process properties, empty on error.
+Dictionary os_proc_info(int pid)
+{
+ Dictionary pinfo = ARRAY_DICT_INIT;
+ PROCESSENTRY32 pe;
+
+ // Snapshot of all processes. This is used instead of:
+ // OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, …)
+ // to avoid ERROR_PARTIAL_COPY. https://stackoverflow.com/a/29942376
+ HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (h == INVALID_HANDLE_VALUE) {
+ return pinfo; // Return empty.
+ }
+
+ pe.dwSize = sizeof(PROCESSENTRY32);
+ // Get root process.
+ if (!Process32First(h, &pe)) {
+ CloseHandle(h);
+ return pinfo; // Return empty.
+ }
+ // Find the process.
+ do {
+ if (pe.th32ProcessID == (DWORD)pid) {
+ break;
+ }
+ } while (Process32Next(h, &pe));
+ CloseHandle(h);
+
+ if (pe.th32ProcessID == (DWORD)pid) {
+ PUT(pinfo, "pid", INTEGER_OBJ(pid));
+ PUT(pinfo, "ppid", INTEGER_OBJ((int)pe.th32ParentProcessID));
+ PUT(pinfo, "name", STRING_OBJ(cstr_to_string(pe.szExeFile)));
+ }
+
+ return pinfo;
+}
+#endif
diff --git a/src/nvim/os/process.h b/src/nvim/os/process.h
new file mode 100644
index 0000000000..1722d56bd3
--- /dev/null
+++ b/src/nvim/os/process.h
@@ -0,0 +1,11 @@
+#ifndef NVIM_OS_PROCESS_H
+#define NVIM_OS_PROCESS_H
+
+#include <stddef.h>
+#include "nvim/api/private/defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/process.h.generated.h"
+#endif
+
+#endif // NVIM_OS_PROCESS_H
diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c
index ee3ab96a83..dfe2cfbb8d 100644
--- a/src/nvim/os/pty_process_unix.c
+++ b/src/nvim/os/pty_process_unix.c
@@ -36,31 +36,43 @@
# include "os/pty_process_unix.c.generated.h"
#endif
+/// termios saved at startup (for TUI) or initialized by pty_process_spawn().
+static struct termios termios_default;
+
+/// Saves the termios properties associated with `tty_fd`.
+///
+/// @param tty_fd TTY file descriptor, or -1 if not in a terminal.
+void pty_process_save_termios(int tty_fd)
+{
+ if (tty_fd == -1 || tcgetattr(tty_fd, &termios_default) != 0) {
+ return;
+ }
+}
+
/// @returns zero on success, or negative error code
int pty_process_spawn(PtyProcess *ptyproc)
FUNC_ATTR_NONNULL_ALL
{
- static struct termios termios;
- if (!termios.c_cflag) {
- init_termios(&termios);
+ if (!termios_default.c_cflag) {
+ // TODO(jkeyes): We could pass NULL to forkpty() instead ...
+ init_termios(&termios_default);
}
int status = 0; // zero or negative error code (libuv convention)
Process *proc = (Process *)ptyproc;
- assert(!proc->err);
+ assert(proc->err.closed);
uv_signal_start(&proc->loop->children_watcher, chld_handler, SIGCHLD);
ptyproc->winsize = (struct winsize){ ptyproc->height, ptyproc->width, 0, 0 };
uv_disable_stdio_inheritance();
int master;
- int pid = forkpty(&master, NULL, &termios, &ptyproc->winsize);
+ int pid = forkpty(&master, NULL, &termios_default, &ptyproc->winsize);
if (pid < 0) {
status = -errno;
ELOG("forkpty failed: %s", strerror(errno));
return status;
} else if (pid == 0) {
- init_child(ptyproc);
- abort();
+ init_child(ptyproc); // never returns
}
// make sure the master file descriptor is non blocking
@@ -83,12 +95,12 @@ int pty_process_spawn(PtyProcess *ptyproc)
goto error;
}
- if (proc->in
- && (status = set_duplicating_descriptor(master, &proc->in->uv.pipe))) {
+ if (!proc->in.closed
+ && (status = set_duplicating_descriptor(master, &proc->in.uv.pipe))) {
goto error;
}
- if (proc->out
- && (status = set_duplicating_descriptor(master, &proc->out->uv.pipe))) {
+ if (!proc->out.closed
+ && (status = set_duplicating_descriptor(master, &proc->out.uv.pipe))) {
goto error;
}
@@ -133,8 +145,12 @@ void pty_process_teardown(Loop *loop)
uv_signal_stop(&loop->children_watcher);
}
-static void init_child(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL
+static void init_child(PtyProcess *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
{
+ // New session/process-group. #6530
+ setsid();
+
unsetenv("COLUMNS");
unsetenv("LINES");
unsetenv("TERMCAP");
@@ -150,14 +166,15 @@ static void init_child(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL
Process *proc = (Process *)ptyproc;
if (proc->cwd && os_chdir(proc->cwd) != 0) {
- fprintf(stderr, "chdir failed: %s\n", strerror(errno));
+ ELOG("chdir failed: %s", strerror(errno));
return;
}
char *prog = ptyproc->process.argv[0];
setenv("TERM", ptyproc->term_name ? ptyproc->term_name : "ansi", 1);
execvp(prog, ptyproc->process.argv);
- fprintf(stderr, "execvp failed: %s: %s\n", strerror(errno), prog);
+ ELOG("execvp failed: %s: %s", strerror(errno), prog);
+ _exit(122); // 122 is EXEC_FAILED in the Vim source.
}
static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL
diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c
new file mode 100644
index 0000000000..a6774a6275
--- /dev/null
+++ b/src/nvim/os/pty_process_win.c
@@ -0,0 +1,413 @@
+// 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 <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <winpty_constants.h>
+
+#include "nvim/os/os.h"
+#include "nvim/ascii.h"
+#include "nvim/memory.h"
+#include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8
+#include "nvim/os/pty_process_win.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/pty_process_win.c.generated.h"
+#endif
+
+static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused)
+ FUNC_ATTR_NONNULL_ALL
+{
+ PtyProcess *ptyproc = (PtyProcess *)context;
+ Process *proc = (Process *)ptyproc;
+
+ 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);
+}
+
+/// @returns zero on success, or negative error code.
+int pty_process_spawn(PtyProcess *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+ Process *proc = (Process *)ptyproc;
+ int status = 0;
+ winpty_error_ptr_t err = NULL;
+ winpty_config_t *cfg = NULL;
+ winpty_spawn_config_t *spawncfg = NULL;
+ winpty_t *winpty_object = NULL;
+ char *in_name = NULL;
+ char *out_name = NULL;
+ HANDLE process_handle = NULL;
+ uv_connect_t *in_req = NULL;
+ uv_connect_t *out_req = NULL;
+ wchar_t *cmd_line = NULL;
+ wchar_t *cwd = NULL;
+ const char *emsg = NULL;
+
+ assert(proc->err.closed);
+
+ cfg = winpty_config_new(WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION, &err);
+ if (cfg == NULL) {
+ emsg = "Failed, winpty_config_new.";
+ goto cleanup;
+ }
+
+ winpty_config_set_initial_size(cfg, ptyproc->width, ptyproc->height);
+ winpty_object = winpty_open(cfg, &err);
+ if (winpty_object == NULL) {
+ emsg = "Failed, winpty_open.";
+ goto cleanup;
+ }
+
+ status = utf16_to_utf8(winpty_conin_name(winpty_object), &in_name);
+ if (status != 0) {
+ emsg = "Failed to convert in_name from utf16 to utf8.";
+ goto cleanup;
+ }
+
+ status = utf16_to_utf8(winpty_conout_name(winpty_object), &out_name);
+ if (status != 0) {
+ emsg = "Failed to convert out_name from utf16 to utf8.";
+ goto cleanup;
+ }
+
+ if (!proc->in.closed) {
+ in_req = xmalloc(sizeof(uv_connect_t));
+ uv_pipe_connect(
+ in_req,
+ &proc->in.uv.pipe,
+ in_name,
+ pty_process_connect_cb);
+ }
+
+ if (!proc->out.closed) {
+ out_req = xmalloc(sizeof(uv_connect_t));
+ uv_pipe_connect(
+ out_req,
+ &proc->out.uv.pipe,
+ out_name,
+ pty_process_connect_cb);
+ }
+
+ if (proc->cwd != NULL) {
+ status = utf8_to_utf16(proc->cwd, &cwd);
+ if (status != 0) {
+ emsg = "Failed to convert pwd form utf8 to utf16.";
+ goto cleanup;
+ }
+ }
+
+ status = build_cmd_line(proc->argv, &cmd_line,
+ os_shell_is_cmdexe(proc->argv[0]));
+ if (status != 0) {
+ emsg = "Failed to convert cmd line form utf8 to utf16.";
+ 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 = "Failed winpty_spawn_config_new.";
+ 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 spawn process.";
+ } else {
+ emsg = "Failed winpty_spawn.";
+ }
+ goto cleanup;
+ }
+ proc->pid = (int)GetProcessId(process_handle);
+
+ if (!RegisterWaitForSingleObject(
+ &ptyproc->finish_wait,
+ process_handle,
+ pty_process_finish1,
+ ptyproc,
+ INFINITE,
+ WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE)) {
+ abort();
+ }
+
+ // Wait until pty_process_connect_cb is called.
+ while ((in_req != NULL && in_req->handle != NULL)
+ || (out_req != NULL && out_req->handle != NULL)) {
+ uv_run(&proc->loop->uv, UV_RUN_ONCE);
+ }
+
+ ptyproc->winpty_object = winpty_object;
+ ptyproc->process_handle = process_handle;
+ winpty_object = NULL;
+ process_handle = NULL;
+
+cleanup:
+ if (status) {
+ // In the case of an error of MultiByteToWideChar or CreateProcessW.
+ ELOG("%s error code: %d", emsg, status);
+ status = os_translate_sys_error(status);
+ } else if (err != NULL) {
+ status = (int)winpty_error_code(err);
+ ELOG("%s error code: %d", emsg, status);
+ status = translate_winpty_error(status);
+ }
+ winpty_error_free(err);
+ winpty_config_free(cfg);
+ winpty_spawn_config_free(spawncfg);
+ winpty_free(winpty_object);
+ xfree(in_name);
+ xfree(out_name);
+ if (process_handle != NULL) {
+ CloseHandle(process_handle);
+ }
+ xfree(in_req);
+ xfree(out_req);
+ xfree(cmd_line);
+ xfree(cwd);
+ return status;
+}
+
+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);
+ }
+}
+
+void pty_process_close(PtyProcess *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+ Process *proc = (Process *)ptyproc;
+
+ pty_process_close_master(ptyproc);
+
+ if (proc->internal_close_cb) {
+ proc->internal_close_cb(proc);
+ }
+}
+
+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;
+ }
+}
+
+void pty_process_teardown(Loop *loop)
+ FUNC_ATTR_NONNULL_ALL
+{
+}
+
+static void pty_process_connect_cb(uv_connect_t *req, int status)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(status == 0);
+ req->handle = NULL;
+}
+
+static void wait_eof_timer_cb(uv_timer_t *wait_eof_timer)
+ FUNC_ATTR_NONNULL_ALL
+{
+ PtyProcess *ptyproc = wait_eof_timer->data;
+ Process *proc = (Process *)ptyproc;
+
+ if (proc->out.closed || !uv_is_readable(proc->out.uvstream)) {
+ uv_timer_stop(&ptyproc->wait_eof_timer);
+ pty_process_finish2(ptyproc);
+ }
+}
+
+static void pty_process_finish2(PtyProcess *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+ Process *proc = (Process *)ptyproc;
+
+ UnregisterWaitEx(ptyproc->finish_wait, NULL);
+ uv_close((uv_handle_t *)&ptyproc->wait_eof_timer, NULL);
+
+ DWORD exit_code = 0;
+ GetExitCodeProcess(ptyproc->process_handle, &exit_code);
+ proc->status = (int)exit_code;
+
+ CloseHandle(ptyproc->process_handle);
+ ptyproc->process_handle = NULL;
+
+ proc->internal_exit_cb(proc);
+}
+
+/// Build the command line to pass to CreateProcessW.
+///
+/// @param[in] argv Array with string arguments.
+/// @param[out] cmd_line Location where saved builded cmd line.
+///
+/// @returns zero on success, or error code of MultiByteToWideChar function.
+///
+static int build_cmd_line(char **argv, wchar_t **cmd_line, bool is_cmdexe)
+ FUNC_ATTR_NONNULL_ALL
+{
+ size_t utf8_cmd_line_len = 0;
+ size_t argc = 0;
+ QUEUE args_q;
+
+ QUEUE_INIT(&args_q);
+ while (*argv) {
+ size_t buf_len = is_cmdexe ? (strlen(*argv) + 1) : (strlen(*argv) * 2 + 3);
+ ArgNode *arg_node = xmalloc(sizeof(*arg_node));
+ arg_node->arg = xmalloc(buf_len);
+ if (is_cmdexe) {
+ xstrlcpy(arg_node->arg, *argv, buf_len);
+ } else {
+ quote_cmd_arg(arg_node->arg, buf_len, *argv);
+ }
+ utf8_cmd_line_len += strlen(arg_node->arg);
+ QUEUE_INIT(&arg_node->node);
+ QUEUE_INSERT_TAIL(&args_q, &arg_node->node);
+ argc++;
+ argv++;
+ }
+
+ utf8_cmd_line_len += argc;
+ char *utf8_cmd_line = xmalloc(utf8_cmd_line_len);
+ *utf8_cmd_line = NUL;
+ while (1) {
+ QUEUE *head = QUEUE_HEAD(&args_q);
+ QUEUE_REMOVE(head);
+ ArgNode *arg_node = QUEUE_DATA(head, ArgNode, node);
+ xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len);
+ xfree(arg_node->arg);
+ xfree(arg_node);
+ if (QUEUE_EMPTY(&args_q)) {
+ break;
+ } else {
+ xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len);
+ }
+ }
+
+ int result = utf8_to_utf16(utf8_cmd_line, cmd_line);
+ xfree(utf8_cmd_line);
+ return result;
+}
+
+/// Emulate quote_cmd_arg of libuv and quotes command line argument.
+/// Most of the code came from libuv.
+///
+/// @param[out] dest Location where saved quotes argument.
+/// @param dest_remaining Destination buffer size.
+/// @param[in] src Pointer to argument.
+///
+static void quote_cmd_arg(char *dest, size_t dest_remaining, const char *src)
+ FUNC_ATTR_NONNULL_ALL
+{
+ size_t src_len = strlen(src);
+ bool quote_hit = true;
+ char *start = dest;
+
+ if (src_len == 0) {
+ // Need double quotation for empty argument.
+ snprintf(dest, dest_remaining, "\"\"");
+ return;
+ }
+
+ if (NULL == strpbrk(src, " \t\"")) {
+ // No quotation needed.
+ xstrlcpy(dest, src, dest_remaining);
+ return;
+ }
+
+ if (NULL == strpbrk(src, "\"\\")) {
+ // No embedded double quotes or backlashes, so I can just wrap quote marks.
+ // around the whole thing.
+ snprintf(dest, dest_remaining, "\"%s\"", src);
+ return;
+ }
+
+ // Expected input/output:
+ // input : 'hello"world'
+ // output: '"hello\"world"'
+ // input : 'hello""world'
+ // output: '"hello\"\"world"'
+ // input : 'hello\world'
+ // output: 'hello\world'
+ // input : 'hello\\world'
+ // output: 'hello\\world'
+ // input : 'hello\"world'
+ // output: '"hello\\\"world"'
+ // input : 'hello\\"world'
+ // output: '"hello\\\\\"world"'
+ // input : 'hello world\'
+ // output: '"hello world\\"'
+
+ assert(dest_remaining--);
+ *(dest++) = NUL;
+ assert(dest_remaining--);
+ *(dest++) = '"';
+ for (size_t i = src_len; i > 0; i--) {
+ assert(dest_remaining--);
+ *(dest++) = src[i - 1];
+ if (quote_hit && src[i - 1] == '\\') {
+ assert(dest_remaining--);
+ *(dest++) = '\\';
+ } else if (src[i - 1] == '"') {
+ quote_hit = true;
+ assert(dest_remaining--);
+ *(dest++) = '\\';
+ } else {
+ quote_hit = false;
+ }
+ }
+ assert(dest_remaining);
+ *dest = '"';
+
+ while (start < dest) {
+ char tmp = *start;
+ *start = *dest;
+ *dest = tmp;
+ start++;
+ dest--;
+ }
+}
+
+/// Translate winpty error code to libuv error.
+///
+/// @param[in] winpty_errno Winpty error code returned by winpty_error_code
+/// function.
+///
+/// @returns Error code of libuv error.
+int translate_winpty_error(int winpty_errno)
+{
+ if (winpty_errno <= 0) {
+ return winpty_errno; // If < 0 then it's already a libuv error.
+ }
+
+ switch (winpty_errno) {
+ case WINPTY_ERROR_OUT_OF_MEMORY: return UV_ENOMEM;
+ case WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED: return UV_EAI_FAIL;
+ case WINPTY_ERROR_LOST_CONNECTION: return UV_ENOTCONN;
+ case WINPTY_ERROR_AGENT_EXE_MISSING: return UV_ENOENT;
+ case WINPTY_ERROR_UNSPECIFIED: return UV_UNKNOWN;
+ case WINPTY_ERROR_AGENT_DIED: return UV_ESRCH;
+ case WINPTY_ERROR_AGENT_TIMEOUT: return UV_ETIMEDOUT;
+ case WINPTY_ERROR_AGENT_CREATION_FAILED: return UV_EAI_FAIL;
+ default: return UV_UNKNOWN;
+ }
+}
diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h
index 8e2b37a1c1..1a4019e654 100644
--- a/src/nvim/os/pty_process_win.h
+++ b/src/nvim/os/pty_process_win.h
@@ -1,20 +1,27 @@
#ifndef NVIM_OS_PTY_PROCESS_WIN_H
#define NVIM_OS_PTY_PROCESS_WIN_H
-#include "nvim/event/libuv_process.h"
+#include <uv.h>
+#include <winpty.h>
+
+#include "nvim/event/process.h"
+#include "nvim/lib/queue.h"
typedef struct pty_process {
Process process;
char *term_name;
uint16_t width, height;
+ winpty_t *winpty_object;
+ HANDLE finish_wait;
+ HANDLE process_handle;
+ uv_timer_t wait_eof_timer;
} PtyProcess;
-#define pty_process_spawn(job) libuv_process_spawn((LibuvProcess *)job)
-#define pty_process_close(job) libuv_process_close((LibuvProcess *)job)
-#define pty_process_close_master(job) libuv_process_close((LibuvProcess *)job)
-#define pty_process_resize(job, width, height) ( \
- (void)job, (void)width, (void)height, 0)
-#define pty_process_teardown(loop) ((void)loop, 0)
+// Structure used by build_cmd_line()
+typedef struct arg_node {
+ char *arg; // pointer to argument.
+ QUEUE node; // QUEUE structure.
+} ArgNode;
static inline PtyProcess pty_process_init(Loop *loop, void *data)
{
@@ -23,7 +30,14 @@ 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.finish_wait = NULL;
+ rv.process_handle = NULL;
return rv;
}
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/pty_process_win.h.generated.h"
+#endif
+
#endif // NVIM_OS_PTY_PROCESS_WIN_H
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index 9d80a43718..f650a51fe7 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -123,6 +123,9 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args)
if (opts & kShellOptRead) {
output_ptr = &output;
forward_output = false;
+ } else if (opts & kShellOptDoOut) {
+ // Caller has already redirected output
+ forward_output = false;
}
}
@@ -189,6 +192,7 @@ static int do_os_system(char **argv,
{
out_data_decide_throttle(0); // Initialize throttle decider.
out_data_ring(NULL, 0); // Initialize output ring-buffer.
+ bool has_input = (input != NULL && input[0] != '\0');
// the output buffer
DynamicBuffer buf = DYNAMIC_BUFFER_INIT;
@@ -207,16 +211,12 @@ static int do_os_system(char **argv,
char prog[MAXPATHL];
xstrlcpy(prog, argv[0], MAXPATHL);
- Stream in, out, err;
LibuvProcess uvproc = libuv_process_init(&main_loop, &buf);
Process *proc = &uvproc.process;
MultiQueue *events = multiqueue_new_child(main_loop.events);
proc->events = events;
proc->argv = argv;
- proc->in = input != NULL ? &in : NULL;
- proc->out = &out;
- proc->err = &err;
- int status = process_spawn(proc);
+ int status = process_spawn(proc, has_input, true, true);
if (status) {
loop_poll_events(&main_loop, 0);
// Failed, probably 'shell' is not executable.
@@ -231,43 +231,54 @@ static int do_os_system(char **argv,
return -1;
}
- // We want to deal with stream events as fast a possible while queueing
- // process events, so reset everything to NULL. It prevents closing the
+ // Note: unlike process events, stream events are not queued, as we want to
+ // deal with stream events as fast a possible. It prevents closing the
// streams while there's still data in the OS buffer (due to the process
// exiting before all data is read).
- if (input != NULL) {
- proc->in->events = NULL;
- wstream_init(proc->in, 0);
+ if (has_input) {
+ wstream_init(&proc->in, 0);
}
- proc->out->events = NULL;
- rstream_init(proc->out, 0);
- rstream_start(proc->out, data_cb, &buf);
- proc->err->events = NULL;
- rstream_init(proc->err, 0);
- rstream_start(proc->err, data_cb, &buf);
+ rstream_init(&proc->out, 0);
+ rstream_start(&proc->out, data_cb, &buf);
+ rstream_init(&proc->err, 0);
+ rstream_start(&proc->err, data_cb, &buf);
// write the input, if any
- if (input) {
- WBuffer *input_buffer = wstream_new_buffer((char *) input, len, 1, NULL);
+ if (has_input) {
+ WBuffer *input_buffer = wstream_new_buffer((char *)input, len, 1, NULL);
- if (!wstream_write(&in, input_buffer)) {
+ if (!wstream_write(&proc->in, input_buffer)) {
// couldn't write, stop the process and tell the user about it
process_stop(proc);
return -1;
}
// close the input stream after everything is written
- wstream_set_write_cb(&in, shell_write_cb, NULL);
+ wstream_set_write_cb(&proc->in, shell_write_cb, NULL);
}
// Invoke busy_start here so LOOP_PROCESS_EVENTS_UNTIL will not change the
// busy state.
ui_busy_start();
ui_flush();
+ if (forward_output) {
+ msg_sb_eol();
+ msg_start();
+ msg_no_more = true;
+ lines_left = -1;
+ }
int exitcode = process_wait(proc, -1, NULL);
if (!got_int && out_data_decide_throttle(0)) {
// Last chunk of output was skipped; display it now.
out_data_ring(NULL, SIZE_MAX);
}
+ if (forward_output) {
+ // caller should decide if wait_return is invoked
+ no_wait_return++;
+ msg_end();
+ no_wait_return--;
+ msg_no_more = false;
+ }
+
ui_busy_stop();
// prepare the out parameters if requested
@@ -411,7 +422,7 @@ static void out_data_ring(char *output, size_t size)
}
if (output == NULL && size == SIZE_MAX) { // Print mode
- out_data_append_to_screen(last_skipped, last_skipped_len, true);
+ out_data_append_to_screen(last_skipped, &last_skipped_len, true);
return;
}
@@ -439,78 +450,58 @@ static void out_data_ring(char *output, size_t size)
/// @param output Data to append to screen lines.
/// @param remaining Size of data.
/// @param new_line If true, next data output will be on a new line.
-static void out_data_append_to_screen(char *output, size_t remaining,
- bool new_line)
+static void out_data_append_to_screen(char *output, size_t *count,
+ bool eof)
{
- static colnr_T last_col = 0; // Column of last row to append to.
-
- size_t off = 0;
- int last_row = (int)Rows - 1;
-
- while (off < remaining) {
- // Found end of line?
- if (output[off] == NL) {
- // Can we start a new line or do we need to continue the last one?
- if (last_col == 0) {
- screen_del_lines(0, 0, 1, (int)Rows, NULL);
+ char *p = output, *end = output + *count;
+ while (p < end) {
+ if (*p == '\n' || *p == '\r' || *p == TAB || *p == BELL) {
+ msg_putchar_attr((uint8_t)(*p), 0);
+ p++;
+ } else {
+ // Note: this is not 100% precise:
+ // 1. we don't check if received continuation bytes are already invalid
+ // and we thus do some buffering that could be avoided
+ // 2. we don't compose chars over buffer boundaries, even if we see an
+ // incomplete UTF-8 sequence that could be composing with the last
+ // complete sequence.
+ // This will be corrected when we switch to vterm based implementation
+ int i = *p ? mb_ptr2len_len((char_u *)p, (int)(end-p)) : 1;
+ if (!eof && i == 1 && utf8len_tab_zero[*(uint8_t *)p] > (end-p)) {
+ *count = (size_t)(p - output);
+ goto end;
}
- screen_puts_len((char_u *)output, (int)off, last_row, last_col, 0);
- last_col = 0;
-
- size_t skip = off + 1;
- output += skip;
- remaining -= skip;
- off = 0;
- continue;
- }
- // TODO(bfredl): using msg_puts would be better until
- // terminal emulation is implemented.
- if (output[off] < 0x20) {
- output[off] = ' ';
+ (void)msg_outtrans_len_attr((char_u *)p, i, 0);
+ p += i;
}
-
- off++;
- }
-
- if (remaining) {
- if (last_col == 0) {
- screen_del_lines(0, 0, 1, (int)Rows, NULL);
- }
- screen_puts_len((char_u *)output, (int)remaining, last_row, last_col, 0);
- last_col += (colnr_T)remaining;
- }
-
- if (new_line) {
- last_col = 0;
}
+end:
ui_flush();
}
static void out_data_cb(Stream *stream, RBuffer *buf, size_t count, void *data,
bool eof)
{
- // We always output the whole buffer, so the buffer can never
- // wrap around.
size_t cnt;
char *ptr = rbuffer_read_ptr(buf, &cnt);
- if (ptr == NULL || cnt == 0) {
- // Nothing to read;
- return;
- }
-
- if (out_data_decide_throttle(cnt)) { // Skip output above a threshold.
+ if (ptr != NULL && cnt > 0
+ && out_data_decide_throttle(cnt)) { // Skip output above a threshold.
// Save the skipped output. If it is the final chunk, we display it later.
out_data_ring(ptr, cnt);
} else {
- out_data_append_to_screen(ptr, cnt, eof);
+ out_data_append_to_screen(ptr, &cnt, eof);
}
if (cnt) {
rbuffer_consumed(buf, cnt);
}
+
+ // Move remaining data to start of buffer, so the buffer can never
+ // wrap around.
+ rbuffer_reset(buf);
}
/// Parses a command string into a sequence of words, taking quotes into
@@ -599,14 +590,10 @@ static void read_input(DynamicBuffer *buf)
if (len == l) {
// Finished a line, add a NL, unless this line should not have one.
- // FIXME need to make this more readable
if (lnum != curbuf->b_op_end.lnum
- || (!curbuf->b_p_bin
- && curbuf->b_p_fixeol)
+ || (!curbuf->b_p_bin && curbuf->b_p_fixeol)
|| (lnum != curbuf->b_no_eol_lnum
- && (lnum !=
- curbuf->b_ml.ml_line_count
- || curbuf->b_p_eol))) {
+ && (lnum != curbuf->b_ml.ml_line_count || curbuf->b_p_eol))) {
dynamic_buffer_ensure(buf, buf->len + 1);
buf->data[buf->len++] = NL;
}
@@ -688,10 +675,6 @@ static void shell_write_cb(Stream *stream, void *data, int status)
msg_schedule_emsgf(_("E5677: Error writing input to shell-command: %s"),
uv_err_name(status));
}
- if (stream->closed) { // Process may have exited before this write.
- WLOG("stream was already closed");
- return;
- }
stream_close(stream, NULL, NULL);
}
diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c
index fd6d3b32e4..732be072e1 100644
--- a/src/nvim/os/signal.c
+++ b/src/nvim/os/signal.c
@@ -10,6 +10,7 @@
#endif
#include "nvim/ascii.h"
+#include "nvim/log.h"
#include "nvim/vim.h"
#include "nvim/globals.h"
#include "nvim/memline.h"
@@ -162,7 +163,7 @@ static void on_signal(SignalWatcher *handle, int signum, void *data)
}
break;
default:
- fprintf(stderr, "Invalid signal %d", signum);
+ ELOG("invalid signal: %d", signum);
break;
}
}
diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c
index c471352c02..290d421acc 100644
--- a/src/nvim/os/time.c
+++ b/src/nvim/os/time.c
@@ -38,33 +38,33 @@ uint64_t os_hrtime(void)
return uv_hrtime();
}
-/// Sleeps for a certain amount of milliseconds.
+/// Sleeps for `ms` milliseconds.
///
-/// @param milliseconds Number of milliseconds to sleep
+/// @param ms Number of milliseconds to sleep
/// @param ignoreinput If true, only SIGINT (CTRL-C) can interrupt.
-void os_delay(uint64_t milliseconds, bool ignoreinput)
+void os_delay(uint64_t ms, bool ignoreinput)
{
if (ignoreinput) {
- if (milliseconds > INT_MAX) {
- milliseconds = INT_MAX;
+ if (ms > INT_MAX) {
+ ms = INT_MAX;
}
- LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)milliseconds, got_int);
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)ms, got_int);
} else {
- os_microdelay(milliseconds * 1000u, ignoreinput);
+ os_microdelay(ms * 1000u, ignoreinput);
}
}
-/// Sleeps for a certain amount of microseconds.
+/// Sleeps for `us` microseconds.
///
-/// @param ms Number of microseconds to sleep.
+/// @param us Number of microseconds to sleep.
/// @param ignoreinput If true, ignore all input (including SIGINT/CTRL-C).
/// If false, waiting is aborted on any input.
-void os_microdelay(uint64_t ms, bool ignoreinput)
+void os_microdelay(uint64_t us, bool ignoreinput)
{
uint64_t elapsed = 0u;
uint64_t base = uv_hrtime();
// Convert microseconds to nanoseconds, or UINT64_MAX on overflow.
- const uint64_t ns = (ms < UINT64_MAX / 1000u) ? ms * 1000u : UINT64_MAX;
+ const uint64_t ns = (us < UINT64_MAX / 1000u) ? us * 1000u : UINT64_MAX;
uv_mutex_lock(&delay_mutex);
diff --git a/src/nvim/os/unix_defs.h b/src/nvim/os/unix_defs.h
index 5c9daca476..60a2dfa882 100644
--- a/src/nvim/os/unix_defs.h
+++ b/src/nvim/os/unix_defs.h
@@ -1,9 +1,8 @@
#ifndef NVIM_OS_UNIX_DEFS_H
#define NVIM_OS_UNIX_DEFS_H
-// Windows doesn't have unistd.h, so we include it here to avoid numerous
-// instances of `#ifdef WIN32'.
#include <unistd.h>
+#include <sys/param.h>
// POSIX.1-2008 says that NAME_MAX should be in here
#include <limits.h>
diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h
index 7c980c3768..db93f016bf 100644
--- a/src/nvim/os/win_defs.h
+++ b/src/nvim/os/win_defs.h
@@ -30,8 +30,10 @@
#define USE_CRNL
-// We have our own RGB macro in macros.h.
-#undef RGB
+// Windows defines a RGB macro that produces 0x00bbggrr color values for use
+// with GDI. Our macro is different, and we don't use GDI.
+// Duplicated from macros.h to avoid include-order sensitivity.
+#define RGB_(r, g, b) ((r << 16) | (g << 8) | b)
#ifdef _MSC_VER
# ifndef inline
@@ -40,6 +42,9 @@
# ifndef restrict
# define restrict __restrict
# endif
+# ifndef STDIN_FILENO
+# define STDIN_FILENO _fileno(stdin)
+# endif
# ifndef STDOUT_FILENO
# define STDOUT_FILENO _fileno(stdout)
# endif
@@ -55,6 +60,7 @@
#ifdef _MSC_VER
typedef SSIZE_T ssize_t;
+typedef int mode_t;
#endif
#ifndef SSIZE_MAX
@@ -91,4 +97,14 @@ typedef SSIZE_T ssize_t;
# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
#endif
+#ifndef STDIN_FILENO
+# define STDIN_FILENO 0
+#endif
+#ifndef STDOUT_FILENO
+# define STDOUT_FILENO 1
+#endif
+#ifndef STDERR_FILENO
+# define STDERR_FILENO 2
+#endif
+
#endif // NVIM_OS_WIN_DEFS_H