aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/os
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/os')
-rw-r--r--src/nvim/os/dl.c3
-rw-r--r--src/nvim/os/env.c144
-rw-r--r--src/nvim/os/fileio.c100
-rw-r--r--src/nvim/os/fs.c151
-rw-r--r--src/nvim/os/fs_defs.h2
-rw-r--r--src/nvim/os/input.c22
-rw-r--r--src/nvim/os/mem.c3
-rw-r--r--src/nvim/os/os_defs.h13
-rw-r--r--src/nvim/os/pty_process_unix.c36
-rw-r--r--src/nvim/os/pty_process_win.c410
-rw-r--r--src/nvim/os/pty_process_win.h27
-rw-r--r--src/nvim/os/shell.c49
-rw-r--r--src/nvim/os/shell.h2
-rw-r--r--src/nvim/os/signal.c3
-rw-r--r--src/nvim/os/stdpaths.c3
-rw-r--r--src/nvim/os/time.c3
-rw-r--r--src/nvim/os/unix_defs.h2
-rw-r--r--src/nvim/os/users.c3
-rw-r--r--src/nvim/os/win_defs.h21
19 files changed, 808 insertions, 189 deletions
diff --git a/src/nvim/os/dl.c b/src/nvim/os/dl.c
index fef02cc784..267cf5ae4b 100644
--- a/src/nvim/os/dl.c
+++ b/src/nvim/os/dl.c
@@ -1,3 +1,6 @@
+// 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
+
/// Functions for using external native libraries
#include <stdbool.h>
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index a73d753e46..de0cd10d9c 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -1,11 +1,11 @@
-// env.c -- environment variable access
+// 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>
+// Environment inspection
+#include <assert.h>
#include <uv.h>
-// vim.h must be included before charset.h (and possibly others) or things
-// blow up
#include "nvim/vim.h"
#include "nvim/ascii.h"
#include "nvim/charset.h"
@@ -118,7 +118,6 @@ char *os_getenvname_at_index(size_t index)
return name;
}
-
/// Get the process ID of the Neovim process.
///
/// @return the process ID.
@@ -145,10 +144,27 @@ void os_get_hostname(char *hostname, size_t size)
} else {
xstrlcpy(hostname, vutsname.nodename, size);
}
+#elif defined(WIN32)
+ WCHAR host_utf16[MAX_COMPUTERNAME_LENGTH + 1];
+ DWORD host_wsize = sizeof(host_utf16) / sizeof(host_utf16[0]);
+ if (GetComputerNameW(host_utf16, &host_wsize) == 0) {
+ *hostname = '\0';
+ DWORD err = GetLastError();
+ EMSG2("GetComputerNameW failed: %d", err);
+ return;
+ }
+ host_utf16[host_wsize] = '\0';
+
+ char *host_utf8;
+ int conversion_result = utf16_to_utf8(host_utf16, &host_utf8);
+ if (conversion_result != 0) {
+ EMSG2("utf16_to_utf8 failed: %d", conversion_result);
+ return;
+ }
+ xstrlcpy(hostname, host_utf8, size);
+ xfree(host_utf8);
#else
- // TODO(unknown): Implement this for windows.
- // See the implementation used in vim:
- // https://code.google.com/p/vim/source/browse/src/os_win32.c?r=6b69d8dde19e32909f4ee3a6337e6a2ecfbb6f72#2899
+ EMSG("os_get_hostname failed: missing uname()");
*hostname = '\0';
#endif
}
@@ -505,10 +521,11 @@ static char *remove_tail(char *path, char *pend, char *dirname)
return pend;
}
-/// Iterate over colon-separated list
+/// Iterate over a delimited list.
///
/// @note Environment variables must not be modified during iteration.
///
+/// @param[in] delim Delimiter character.
/// @param[in] val Value of the environment variable to iterate over.
/// @param[in] iter Pointer used for iteration. Must be NULL on first
/// iteration.
@@ -517,18 +534,19 @@ static char *remove_tail(char *path, char *pend, char *dirname)
/// @param[out] len Location where current directory length should be saved.
///
/// @return Next iter argument value or NULL when iteration should stop.
-const void *vim_colon_env_iter(const char *const val,
- const void *const iter,
- const char **const dir,
- size_t *const len)
- FUNC_ATTR_NONNULL_ARG(1, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT
+const void *vim_env_iter(const char delim,
+ const char *const val,
+ const void *const iter,
+ const char **const dir,
+ size_t *const len)
+ FUNC_ATTR_NONNULL_ARG(2, 4, 5) FUNC_ATTR_WARN_UNUSED_RESULT
{
const char *varval = (const char *) iter;
if (varval == NULL) {
varval = val;
}
*dir = varval;
- const char *const dirend = strchr(varval, ':');
+ const char *const dirend = strchr(varval, delim);
if (dirend == NULL) {
*len = strlen(varval);
return NULL;
@@ -538,10 +556,11 @@ const void *vim_colon_env_iter(const char *const val,
}
}
-/// Iterate over colon-separated list in reverse order
+/// Iterate over a delimited list in reverse order.
///
/// @note Environment variables must not be modified during iteration.
///
+/// @param[in] delim Delimiter character.
/// @param[in] val Value of the environment variable to iterate over.
/// @param[in] iter Pointer used for iteration. Must be NULL on first
/// iteration.
@@ -550,18 +569,19 @@ const void *vim_colon_env_iter(const char *const val,
/// @param[out] len Location where current directory length should be saved.
///
/// @return Next iter argument value or NULL when iteration should stop.
-const void *vim_colon_env_iter_rev(const char *const val,
- const void *const iter,
- const char **const dir,
- size_t *const len)
- FUNC_ATTR_NONNULL_ARG(1, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT
+const void *vim_env_iter_rev(const char delim,
+ const char *const val,
+ const void *const iter,
+ const char **const dir,
+ size_t *const len)
+ FUNC_ATTR_NONNULL_ARG(2, 4, 5) FUNC_ATTR_WARN_UNUSED_RESULT
{
const char *varend = (const char *) iter;
if (varend == NULL) {
varend = val + strlen(val) - 1;
}
- const size_t varlen = (size_t) (varend - val) + 1;
- const char *const colon = xmemrchr(val, ':', varlen);
+ const size_t varlen = (size_t)(varend - val) + 1;
+ const char *const colon = xmemrchr(val, (uint8_t)delim, varlen);
if (colon == NULL) {
*len = varlen;
*dir = val;
@@ -580,6 +600,9 @@ const void *vim_colon_env_iter_rev(const char *const val,
/// @param name Environment variable to expand
char *vim_getenv(const char *name)
{
+ // init_path() should have been called before now.
+ assert(get_vim_var_str(VV_PROGPATH)[0] != NUL);
+
const char *kos_env_path = os_getenv(name);
if (kos_env_path != NULL) {
return xstrdup(kos_env_path);
@@ -615,21 +638,20 @@ char *vim_getenv(const char *name)
vim_path = (char *)p_hf;
}
+ char exe_name[MAXPATHL];
// Find runtime path relative to the nvim binary: ../share/nvim/runtime
if (vim_path == NULL) {
- char exe_name[MAXPATHL];
- size_t exe_name_len = MAXPATHL;
- if (os_exepath(exe_name, &exe_name_len) == 0) {
- 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/"
- if (append_path(
- exe_name,
- "share" _PATHSEPSTR "nvim" _PATHSEPSTR "runtime" _PATHSEPSTR,
- MAXPATHL) == OK) {
- vim_path = exe_name;
- }
+ 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/"
+ if (append_path(
+ exe_name,
+ "share" _PATHSEPSTR "nvim" _PATHSEPSTR "runtime" _PATHSEPSTR,
+ MAXPATHL) == OK) {
+ vim_path = exe_name; // -V507
}
}
@@ -662,6 +684,7 @@ char *vim_getenv(const char *name)
vim_path = NULL;
}
}
+ assert(vim_path != exe_name);
}
#ifdef HAVE_PATHDEF
@@ -703,7 +726,8 @@ char *vim_getenv(const char *name)
/// @param dstlen Maximum length of the result
/// @param one If true, only replace one file name, including spaces and commas
/// in the file name
-void home_replace(buf_T *buf, char_u *src, char_u *dst, int dstlen, bool one)
+void home_replace(const buf_T *const buf, const char_u *src,
+ char_u *dst, size_t dstlen, bool one)
{
size_t dirlen = 0, envlen = 0;
size_t len;
@@ -717,7 +741,7 @@ void home_replace(buf_T *buf, char_u *src, char_u *dst, int dstlen, bool one)
* If the file is a help file, remove the path completely.
*/
if (buf != NULL && buf->b_help) {
- STRCPY(dst, path_tail(src));
+ xstrlcpy((char *)dst, (char *)path_tail(src), dstlen);
return;
}
@@ -809,7 +833,7 @@ char_u * home_replace_save(buf_T *buf, char_u *src) FUNC_ATTR_NONNULL_RET
len += STRLEN(src);
}
char_u *dst = xmalloc(len);
- home_replace(buf, src, dst, (int)len, true);
+ home_replace(buf, src, dst, len, true);
return dst;
}
@@ -888,3 +912,45 @@ bool os_setenv_append_path(const char *fname)
}
return false;
}
+
+/// Returns true if the terminal can be assumed to silently ignore unknown
+/// control codes.
+bool os_term_is_nice(void)
+{
+#if defined(__APPLE__) || defined(WIN32)
+ return true;
+#else
+ const char *vte_version = os_getenv("VTE_VERSION");
+ if ((vte_version && atoi(vte_version) >= 3900)
+ || os_getenv("KONSOLE_PROFILE_NAME")
+ || os_getenv("KONSOLE_DBUS_SESSION")) {
+ return true;
+ }
+ const char *termprg = os_getenv("TERM_PROGRAM");
+ if (termprg && striequal(termprg, "iTerm.app")) {
+ return true;
+ }
+ const char *term = os_getenv("TERM");
+ if (term && strncmp(term, "rxvt", 4) == 0) {
+ return true;
+ }
+ return false;
+#endif
+}
+
+/// Returns true if `sh` looks like it resolves to "cmd.exe".
+bool os_shell_is_cmdexe(const char *sh)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (*sh == NUL) {
+ return false;
+ }
+ if (striequal(sh, "$COMSPEC")) {
+ const char *comspec = os_getenv("COMSPEC");
+ return striequal("cmd.exe", (char *)path_tail((char_u *)comspec));
+ }
+ if (striequal(sh, "cmd.exe") || striequal(sh, "cmd")) {
+ return true;
+ }
+ return striequal("cmd.exe", (char *)path_tail((char_u *)sh));
+}
diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c
index 7668a867a0..b040904d0b 100644
--- a/src/nvim/os/fileio.c
+++ b/src/nvim/os/fileio.c
@@ -1,3 +1,6 @@
+// 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
+
/// @file fileio.c
///
/// Buffered reading/writing to a file. Unlike fileio.c this is not dealing with
@@ -23,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"
@@ -46,6 +50,7 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname,
{
int os_open_flags = 0;
TriState wr = kNone;
+ // -V:FLAG:501
#define FLAG(flags, flag, fcntl_flags, wrval, cond) \
do { \
if (flags & flag) { \
@@ -78,7 +83,7 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname,
if (fd < 0) {
return fd;
}
- return file_open_fd(ret_fp, fd, flags, mode);
+ return file_open_fd(ret_fp, fd, flags);
}
/// Wrap file descriptor with FileDescriptor structure
@@ -92,15 +97,14 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname,
/// @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.
-/// @param[in] mode Permissions for the newly created file (ignored if flags
-/// does not have FILE_CREATE\*).
///
/// @return Error code (@see os_strerror()) or 0. Currently always returns 0.
int file_open_fd(FileDescriptor *const ret_fp, const int fd,
- const int flags, const int mode)
+ const int flags)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- ret_fp->wr = !!(flags & (kFileCreate|kFileCreateOnly
+ ret_fp->wr = !!(flags & (kFileCreate
+ |kFileCreateOnly
|kFileTruncate
|kFileAppend
|kFileWriteOnly));
@@ -130,7 +134,7 @@ int file_open_fd(FileDescriptor *const ret_fp, const int fd,
/// @return [allocated] Opened file or NULL in case of error.
FileDescriptor *file_open_new(int *const error, const char *const fname,
const int flags, const int mode)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
FileDescriptor *const fp = xmalloc(sizeof(*fp));
if ((*error = file_open(fp, fname, flags, mode)) != 0) {
@@ -151,11 +155,11 @@ FileDescriptor *file_open_new(int *const error, const char *const fname,
///
/// @return [allocated] Opened file or NULL in case of error.
FileDescriptor *file_open_fd_new(int *const error, const int fd,
- const int flags, const int mode)
+ const int flags)
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, flags, mode)) != 0) {
+ if ((*error = file_open_fd(fp, fd, flags)) != 0) {
xfree(fp);
return NULL;
}
@@ -165,27 +169,31 @@ FileDescriptor *file_open_fd_new(int *const error, const int fd,
/// Close file and free its buffer
///
/// @param[in,out] fp File to close.
+/// @param[in] do_fsync If true, use fsync() to write changes to disk.
///
/// @return 0 or error code.
-int file_close(FileDescriptor *const fp) FUNC_ATTR_NONNULL_ALL
+int file_close(FileDescriptor *const fp, const bool do_fsync)
+ FUNC_ATTR_NONNULL_ALL
{
- const int error = file_fsync(fp);
- const int error2 = os_close(fp->fd);
+ const int flush_error = (do_fsync ? file_fsync(fp) : file_flush(fp));
+ const int close_error = os_close(fp->fd);
rbuffer_free(fp->rv);
- if (error2 != 0) {
- return error2;
+ if (close_error != 0) {
+ return close_error;
}
- return error;
+ return flush_error;
}
/// Close and free file obtained using file_open_new()
///
/// @param[in,out] fp File to close.
+/// @param[in] do_fsync If true, use fsync() to write changes to disk.
///
/// @return 0 or error code.
-int file_free(FileDescriptor *const fp) FUNC_ATTR_NONNULL_ALL
+int file_free(FileDescriptor *const fp, const bool do_fsync)
+ FUNC_ATTR_NONNULL_ALL
{
- const int ret = file_close(fp);
+ const int ret = file_close(fp, do_fsync);
xfree(fp);
return ret;
}
@@ -195,21 +203,36 @@ int file_free(FileDescriptor *const fp) FUNC_ATTR_NONNULL_ALL
/// @param[in,out] fp File to work with.
///
/// @return 0 or error code.
-int file_fsync(FileDescriptor *const fp)
+int file_flush(FileDescriptor *const fp)
FUNC_ATTR_NONNULL_ALL
{
if (!fp->wr) {
return 0;
}
file_rb_write_full_cb(fp->rv, fp);
- if (fp->_error != 0) {
- const int error = fp->_error;
- fp->_error = 0;
- return error;
+ const int error = fp->_error;
+ fp->_error = 0;
+ return error;
+}
+
+/// Flush file modifications to disk and run fsync()
+///
+/// @param[in,out] fp File to work with.
+///
+/// @return 0 or error code.
+int file_fsync(FileDescriptor *const fp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (!fp->wr) {
+ return 0;
}
- const int error = os_fsync(fp->fd);
- if (error != UV_EINVAL && error != UV_EROFS) {
- return error;
+ const int flush_error = file_flush(fp);
+ if (flush_error != 0) {
+ return flush_error;
+ }
+ const int fsync_error = os_fsync(fp->fd);
+ if (fsync_error != UV_EINVAL && fsync_error != UV_EROFS) {
+ return fsync_error;
}
return 0;
}
@@ -380,3 +403,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 383a2ac682..87bab93420 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -1,3 +1,6 @@
+// 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
+
// fs.c -- filesystem access
#include <stdbool.h>
#include <stddef.h>
@@ -58,9 +61,9 @@ void fs_init(void)
}
-/// Change to the given directory.
+/// Changes the current directory to `path`.
///
-/// @return `0` on success, a libuv error code on failure.
+/// @return 0 on success, or negative error code.
int os_chdir(const char *path)
FUNC_ATTR_NONNULL_ALL
{
@@ -91,11 +94,11 @@ int os_dirname(char_u *buf, size_t len)
/// Check if the given path is a directory and not a symlink to a directory.
/// @return `true` if `name` is a directory and NOT a symlink to a directory.
/// `false` if `name` is not a directory or if an error occurred.
-bool os_isrealdir(const char_u *name)
+bool os_isrealdir(const char *name)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
- if (uv_fs_lstat(&fs_loop, &request, (char *)name, NULL) != kLibuvSuccess) {
+ if (uv_fs_lstat(&fs_loop, &request, name, NULL) != kLibuvSuccess) {
return false;
}
if (S_ISLNK(request.statbuf.st_mode)) {
@@ -111,7 +114,7 @@ bool os_isrealdir(const char_u *name)
bool os_isdir(const char_u *name)
FUNC_ATTR_NONNULL_ALL
{
- int32_t mode = os_getperm(name);
+ int32_t mode = os_getperm((const char *)name);
if (mode < 0) {
return false;
}
@@ -128,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;
@@ -152,52 +146,61 @@ 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);
- 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
+ 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 (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.
+/// May fail if procfs is missing. #6734
+/// @see path_exepath
///
-/// @param[out] buffer Returns the path string.
+/// @param[out] buffer Full path to the executable.
/// @param[in] size Size of `buffer`.
///
-/// @return `0` on success, or libuv error code on failure.
+/// @return 0 on success, or libuv error code.
int os_exepath(char *buffer, size_t *size)
FUNC_ATTR_NONNULL_ALL
{
@@ -236,7 +239,8 @@ bool os_can_exe(const char_u *name, char_u **abspath, bool use_path)
pathext);
#else
// Must have path separator, cannot execute files in the current directory.
- bool ok = gettail_dir(name) != name && is_executable((char *)name);
+ const bool ok = ((const char_u *)gettail_dir((const char *)name) != name
+ && is_executable((char *)name));
#endif
if (ok) {
if (abspath != NULL) {
@@ -254,7 +258,7 @@ bool os_can_exe(const char_u *name, char_u **abspath, bool use_path)
static bool is_executable(const char *name)
FUNC_ATTR_NONNULL_ALL
{
- int32_t mode = os_getperm((char_u *)name);
+ int32_t mode = os_getperm((const char *)name);
if (mode < 0) {
return false;
@@ -383,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;
@@ -628,10 +634,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;
@@ -642,11 +651,10 @@ static int os_stat(const char *name, uv_stat_t *statbuf)
/// Get the file permissions for a given file.
///
/// @return libuv error code on error.
-int32_t os_getperm(const char_u *name)
- FUNC_ATTR_NONNULL_ALL
+int32_t os_getperm(const char *name)
{
uv_stat_t statbuf;
- int stat_result = os_stat((char *)name, &statbuf);
+ int stat_result = os_stat(name, &statbuf);
if (stat_result == kLibuvSuccess) {
return (int32_t)statbuf.st_mode;
} else {
@@ -657,11 +665,11 @@ int32_t os_getperm(const char_u *name)
/// Set the permission of a file.
///
/// @return `OK` for success, `FAIL` for failure.
-int os_setperm(const char_u *name, int perm)
+int os_setperm(const char *const name, int perm)
FUNC_ATTR_NONNULL_ALL
{
int r;
- RUN_UV_FS_FUNC(r, uv_fs_chmod, (const char *)name, perm, NULL);
+ RUN_UV_FS_FUNC(r, uv_fs_chmod, name, perm, NULL);
return (r == kLibuvSuccess ? OK : FAIL);
}
@@ -682,7 +690,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;
@@ -872,7 +879,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;
}
@@ -883,8 +890,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;
@@ -1015,13 +1025,13 @@ bool os_fileid_equal_fileinfo(const FileID *file_id,
/// When "fname" is the name of a shortcut (*.lnk) resolve the file it points
/// to and return that name in allocated memory.
/// Otherwise NULL is returned.
-char *os_resolve_shortcut(char_u *fname)
+char *os_resolve_shortcut(const char *fname)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC
{
HRESULT hr;
IPersistFile *ppf = NULL;
OLECHAR wsz[MAX_PATH];
char *rfname = NULL;
- int len;
IShellLinkW *pslw = NULL;
WIN32_FIND_DATAW ffdw;
@@ -1030,7 +1040,7 @@ char *os_resolve_shortcut(char_u *fname)
if (fname == NULL) {
return rfname;
}
- len = (int)STRLEN(fname);
+ const size_t len = strlen(fname);
if (len <= 4 || STRNICMP(fname + len - 4, ".lnk", 4) != 0) {
return rfname;
}
@@ -1042,9 +1052,9 @@ char *os_resolve_shortcut(char_u *fname)
&IID_IShellLinkW, (void **)&pslw);
if (hr == S_OK) {
WCHAR *p;
- int conversion_result = utf8_to_utf16((char *)fname, &p);
+ const int conversion_result = utf8_to_utf16(fname, &p);
if (conversion_result != 0) {
- EMSG2("utf8_to_utf16 failed: %s", uv_strerror(conversion_result));
+ EMSG2("utf8_to_utf16 failed: %d", conversion_result);
}
if (p != NULL) {
@@ -1072,7 +1082,7 @@ char *os_resolve_shortcut(char_u *fname)
ZeroMemory(wsz, MAX_PATH * sizeof(WCHAR));
hr = pslw->lpVtbl->GetPath(pslw, wsz, MAX_PATH, &ffdw, 0);
if (hr == S_OK && wsz[0] != NUL) {
- int conversion_result = utf16_to_utf8(wsz, &rfname);
+ const int conversion_result = utf16_to_utf8(wsz, &rfname);
if (conversion_result != 0) {
EMSG2("utf16_to_utf8 failed: %s", uv_strerror(conversion_result));
}
@@ -1099,7 +1109,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/fs_defs.h b/src/nvim/os/fs_defs.h
index 0bd9c37750..2277d926b3 100644
--- a/src/nvim/os/fs_defs.h
+++ b/src/nvim/os/fs_defs.h
@@ -14,7 +14,7 @@ typedef struct {
uint64_t device_id; ///< @private The id of the device containing the file
} FileID;
-#define FILE_ID_EMPTY (FileID) {.inode = 0, .device_id = 0}
+#define FILE_ID_EMPTY (FileID) { .inode = 0, .device_id = 0 }
typedef struct {
uv_fs_t request; ///< @private The request to uv for the directory.
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index 5f0f2ec677..7d6f2abd7f 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -1,3 +1,6 @@
+// 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 <string.h>
#include <stdbool.h>
@@ -20,6 +23,7 @@
#include "nvim/main.h"
#include "nvim/misc1.h"
#include "nvim/state.h"
+#include "nvim/msgpack_rpc/channel.h"
#define READ_BUFFER_SIZE 0xfff
#define INPUT_BUFFER_SIZE (READ_BUFFER_SIZE * 4)
@@ -33,8 +37,9 @@ 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;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/input.c.generated.h"
@@ -182,7 +187,8 @@ size_t input_enqueue(String keys)
while (rbuffer_space(input_buffer) >= 6 && ptr < end) {
uint8_t buf[6] = { 0 };
unsigned int new_size
- = trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, true);
+ = trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, true,
+ true);
if (new_size) {
new_size = handle_mouse_event(&ptr, buf, new_size);
@@ -323,13 +329,25 @@ static unsigned int handle_mouse_event(char **ptr, uint8_t *buf,
return bufsize;
}
+/// @return true if the main loop is blocked and waiting for input.
+bool input_blocking(void)
+{
+ return blocking;
+}
+
static bool input_poll(int ms)
{
if (do_profiling == PROF_YES && ms) {
prof_inchar_enter();
}
+ if ((ms == - 1 || ms > 0) && !events_enabled && !input_eof) {
+ // The pending input provoked a blocking wait. Do special events now. #6247
+ blocking = true;
+ multiqueue_process_events(ch_before_blocking_events);
+ }
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms, input_ready() || input_eof);
+ blocking = false;
if (do_profiling == PROF_YES && ms) {
prof_inchar_exit();
diff --git a/src/nvim/os/mem.c b/src/nvim/os/mem.c
index 871ece7a0e..eccb3c97e5 100644
--- a/src/nvim/os/mem.c
+++ b/src/nvim/os/mem.c
@@ -1,3 +1,6 @@
+// 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
+
/// Functions for accessing system memory information.
#include <uv.h>
diff --git a/src/nvim/os/os_defs.h b/src/nvim/os/os_defs.h
index 88d8f4b750..87f8d214bd 100644
--- a/src/nvim/os/os_defs.h
+++ b/src/nvim/os/os_defs.h
@@ -4,6 +4,7 @@
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
+#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -13,8 +14,12 @@
# include "nvim/os/unix_defs.h"
#endif
-/// File descriptor number used for STDIN
-enum { OS_STDIN_FILENO = STDIN_FILENO };
+/// File descriptor number used for standard IO streams
+enum {
+ OS_STDIN_FILENO = STDIN_FILENO,
+ OS_STDOUT_FILENO = STDOUT_FILENO,
+ OS_STDERR_FILENO = STDERR_FILENO,
+};
#define BASENAMELEN (NAME_MAX - 5)
@@ -30,11 +35,11 @@ enum { OS_STDIN_FILENO = STDIN_FILENO };
// Use up to 5 Mbyte for a buffer.
#ifndef DFLT_MAXMEM
-# define DFLT_MAXMEM (5*1024)
+# define DFLT_MAXMEM (5 * 1024)
#endif
// use up to 10 Mbyte for Vim.
#ifndef DFLT_MAXMEMTOT
-# define DFLT_MAXMEMTOT (10*1024)
+# define DFLT_MAXMEMTOT (10 * 1024)
#endif
// Note: Some systems need both string.h and strings.h (Savage). However,
diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c
index 71a5e13de5..53301e4b53 100644
--- a/src/nvim/os/pty_process_unix.c
+++ b/src/nvim/os/pty_process_unix.c
@@ -1,3 +1,6 @@
+// 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
+
// Some of the code came from pangoterm and libuv
#include <stdbool.h>
#include <stdlib.h>
@@ -9,7 +12,7 @@
#include <sys/ioctl.h>
// forkpty is not in POSIX, so headers are platform-specific
-#if defined(__FreeBSD__)
+#if defined(__FreeBSD__) || defined (__DragonFly__)
# include <libutil.h>
#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
# include <util.h>
@@ -33,23 +36,36 @@
# 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;
@@ -80,12 +96,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;
}
diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c
new file mode 100644
index 0000000000..3c4839a076
--- /dev/null
+++ b/src/nvim/os/pty_process_win.c
@@ -0,0 +1,410 @@
+#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 = 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 20cc589925..1a4019e654 100644
--- a/src/nvim/os/pty_process_win.h
+++ b/src/nvim/os/pty_process_win.h
@@ -1,19 +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)
-#define pty_process_teardown(loop)
+// 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)
{
@@ -22,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 b449cc3d5a..e32c6e05d2 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -1,3 +1,6 @@
+// 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 <string.h>
#include <assert.h>
#include <stdbool.h>
@@ -47,7 +50,7 @@ typedef struct {
/// @param extra_args Extra arguments to the shell, or NULL.
/// @return Newly allocated argument vector. Must be freed with shell_free_argv.
char **shell_build_argv(const char *cmd, const char *extra_args)
- FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC
+ FUNC_ATTR_NONNULL_RET
{
size_t argc = tokenize(p_sh, NULL) + (cmd ? tokenize(p_shcf, NULL) : 0);
char **rv = xmalloc((argc + 4) * sizeof(*rv));
@@ -124,11 +127,9 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args)
}
size_t nread;
-
int exitcode = do_os_system(shell_build_argv((char *)cmd, (char *)extra_args),
input.data, input.len, output_ptr, &nread,
emsg_silent, forward_output);
-
xfree(input.data);
if (output) {
@@ -206,16 +207,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, input != NULL, true, true);
if (status) {
loop_poll_events(&main_loop, 0);
// Failed, probably 'shell' is not executable.
@@ -230,32 +227,29 @@ 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);
+ 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 (!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
@@ -446,7 +440,7 @@ static void out_data_append_to_screen(char *output, size_t remaining,
size_t off = 0;
int last_row = (int)Rows - 1;
- while (off < remaining) {
+ while (output != NULL && 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?
@@ -463,15 +457,16 @@ static void out_data_append_to_screen(char *output, size_t remaining,
continue;
}
- // Translate NUL to SOH
- if (output[off] == NUL) {
- output[off] = 1;
+ // TODO(bfredl): using msg_puts would be better until
+ // terminal emulation is implemented.
+ if (output[off] < 0x20) {
+ output[off] = ' ';
}
off++;
}
- if (remaining) {
+ if (output != NULL && remaining) {
if (last_col == 0) {
screen_del_lines(0, 0, 1, (int)Rows, NULL);
}
@@ -682,10 +677,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.
- ELOG("stream was already closed");
- return;
- }
stream_close(stream, NULL, NULL);
}
diff --git a/src/nvim/os/shell.h b/src/nvim/os/shell.h
index 58960db157..48503f2601 100644
--- a/src/nvim/os/shell.h
+++ b/src/nvim/os/shell.h
@@ -1,6 +1,8 @@
#ifndef NVIM_OS_SHELL_H
#define NVIM_OS_SHELL_H
+#include <stdio.h>
+
#include "nvim/types.h"
// Flags for os_call_shell() second argument
diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c
index 1ac6d3f5e1..fd6d3b32e4 100644
--- a/src/nvim/os/signal.c
+++ b/src/nvim/os/signal.c
@@ -1,3 +1,6 @@
+// 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>
diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c
index afb9bdec31..a41fb7c621 100644
--- a/src/nvim/os/stdpaths.c
+++ b/src/nvim/os/stdpaths.c
@@ -1,3 +1,6 @@
+// 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 <stdbool.h>
#include "nvim/os/stdpaths_defs.h"
diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c
index 8ce2ecf4f4..c471352c02 100644
--- a/src/nvim/os/time.c
+++ b/src/nvim/os/time.c
@@ -1,3 +1,6 @@
+// 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 <stdint.h>
#include <stdbool.h>
diff --git a/src/nvim/os/unix_defs.h b/src/nvim/os/unix_defs.h
index c98aa88bfa..5c9daca476 100644
--- a/src/nvim/os/unix_defs.h
+++ b/src/nvim/os/unix_defs.h
@@ -8,7 +8,7 @@
// POSIX.1-2008 says that NAME_MAX should be in here
#include <limits.h>
-#define TEMP_DIR_NAMES {"$TMPDIR", "/tmp", ".", "~"}
+#define TEMP_DIR_NAMES { "$TMPDIR", "/tmp", ".", "~" }
#define TEMP_FILE_PATH_MAXLEN 256
#define HAVE_ACL (HAVE_POSIX_ACL || HAVE_SOLARIS_ACL)
diff --git a/src/nvim/os/users.c b/src/nvim/os/users.c
index 1c94ef0067..82bb918f70 100644
--- a/src/nvim/os/users.c
+++ b/src/nvim/os/users.c
@@ -1,3 +1,6 @@
+// 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
+
// users.c -- operating system user information
#include <uv.h>
diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h
index f3493a7eed..8fd2e51f8b 100644
--- a/src/nvim/os/win_defs.h
+++ b/src/nvim/os/win_defs.h
@@ -1,6 +1,10 @@
#ifndef NVIM_OS_WIN_DEFS_H
#define NVIM_OS_WIN_DEFS_H
+#ifndef WIN32
+# error Header must be included only when compiling for Windows.
+#endif
+
// winsock2.h must be first to avoid incompatibilities
// with winsock.h (included by windows.h)
#include <winsock2.h>
@@ -15,7 +19,7 @@
#define NAME_MAX _MAX_PATH
-#define TEMP_DIR_NAMES {"$TMP", "$TEMP", "$USERPROFILE", ""}
+#define TEMP_DIR_NAMES { "$TMPDIR", "$TMP", "$TEMP", "$USERPROFILE", "" }
#define TEMP_FILE_PATH_MAXLEN _MAX_PATH
#define FNAME_ILLEGAL "\"*?><|"
@@ -26,8 +30,13 @@
#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.
+#if defined(RGB)
+# undef RGB
+ // Duplicated from macros.h to avoid include-order sensitivity.
+# define RGB(r, g, b) ((r << 16) | (g << 8) | b)
+#endif
#ifdef _MSC_VER
# ifndef inline
@@ -90,5 +99,11 @@ typedef SSIZE_T ssize_t;
#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