From ff7f22c28b602c84350785624b4b6fc9ae35f950 Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 28 May 2024 13:03:00 +0200 Subject: refactor(fileio): remove useless use of FileDescriptor FileDescriptor is used to buffer togheter many small writes to fewer syscalls. if the data to write already is in a single buffer, it is perfectly fine to just use os_write directly (which will take care of the reverse problem: splitting a too big write into many syscalls) --- src/nvim/os/fileio.c | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index da6fb13768..fbb2be5104 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -365,14 +365,3 @@ ptrdiff_t file_skip(FileDescriptor *const fp, const size_t size) return (ptrdiff_t)read_bytes; } - -/// 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) -{ - semsg(_("E5420: Failed to write to file: %s"), os_strerror(error)); - return -1; -} -- cgit From b386334cdbbc3e9d79773243fdbd53091488e14d Mon Sep 17 00:00:00 2001 From: bfredl Date: Sun, 26 May 2024 11:36:18 +0200 Subject: refactor(shada): remove ShaDaReadDef secondary wrapper `FileDescriptor` is already a wrapper around an fd and a buffer. By allowing to just use the buffer without an fd, it can already handle in-memory reads. --- src/nvim/os/fileio.c | 20 ++++++++++++++++++++ src/nvim/os/fileio_defs.h | 2 ++ 2 files changed, 22 insertions(+) (limited to 'src/nvim/os') diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index da6fb13768..4575c394b8 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -126,6 +126,7 @@ int file_open_fd(FileDescriptor *const ret_fp, const int fd, const int flags) ret_fp->rv->data = ret_fp; ret_fp->rv->full_cb = (rbuffer_callback)&file_rb_write_full_cb; } + ret_fp->bytes_read = 0; return 0; } @@ -140,6 +141,18 @@ int file_open_stdin(FileDescriptor *fp) return error; } +/// opens buffer for reading +void file_open_buffer(FileDescriptor *ret_fp, char *data, size_t len) +{ + ret_fp->wr = false; + ret_fp->non_blocking = false; + ret_fp->fd = -1; + ret_fp->eof = true; + ret_fp->rv = rbuffer_new_wrap_buf(data, len); + ret_fp->_error = 0; + ret_fp->bytes_read = 0; +} + /// Close file and free its buffer /// /// @param[in,out] fp File to close. @@ -149,6 +162,11 @@ int file_open_stdin(FileDescriptor *fp) int file_close(FileDescriptor *const fp, const bool do_fsync) FUNC_ATTR_NONNULL_ALL { + if (fp->fd < 0) { + rbuffer_free(fp->rv); + return 0; + } + const int flush_error = (do_fsync ? file_fsync(fp) : file_flush(fp)); const int close_error = os_close(fp->fd); rbuffer_free(fp->rv); @@ -294,6 +312,7 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, const size_t fp->non_blocking); if (r_ret >= 0) { read_remaining -= (size_t)r_ret; + fp->bytes_read += (size - read_remaining); return (ptrdiff_t)(size - read_remaining); } else if (r_ret < 0) { return r_ret; @@ -314,6 +333,7 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, const size_t called_read = true; } } + fp->bytes_read += (size - read_remaining); return (ptrdiff_t)(size - read_remaining); } diff --git a/src/nvim/os/fileio_defs.h b/src/nvim/os/fileio_defs.h index 3dc8c7b22a..10277d2a7a 100644 --- a/src/nvim/os/fileio_defs.h +++ b/src/nvim/os/fileio_defs.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "nvim/func_attr.h" #include "nvim/rbuffer_defs.h" @@ -13,6 +14,7 @@ typedef struct { bool wr; ///< True if file is in write mode. bool eof; ///< True if end of file was encountered. bool non_blocking; ///< True if EAGAIN should not restart syscalls. + uint64_t bytes_read; ///< total bytes read so far } FileDescriptor; static inline bool file_eof(const FileDescriptor *fp) -- cgit From 064483a2b4a3056baf8eee4424bb81127e531991 Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 28 May 2024 12:52:24 +0200 Subject: refactor(fileio): use a linear buffer for FileDescriptor Using a ring buffer for buffered synchronous fileio is just unnecessary complexity. - when reading, we always consume the _entire_ buffer before getting into syscalls. Thus we reset the buffer to its initial position before when we actually read. - when writing and buffer is full, we always flush the entire buffer before starting to buffer again. So we can reset the buffer to its initial state. Also no static buffers are needed for writing and skipping. Needing an extra copy for each write completely defeated the purpose of a ring buffer (if there had been one) --- src/nvim/os/fileio.c | 274 +++++++++++++++++++++++----------------------- src/nvim/os/fileio_defs.h | 9 +- 2 files changed, 143 insertions(+), 140 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index e58eb96c2e..c87b2d359f 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -120,12 +120,9 @@ int file_open_fd(FileDescriptor *const ret_fp, const int fd, const int flags) assert(!ret_fp->wr || !ret_fp->non_blocking); ret_fp->fd = fd; ret_fp->eof = false; - ret_fp->rv = rbuffer_new(kRWBufferSize); - ret_fp->_error = 0; - if (ret_fp->wr) { - ret_fp->rv->data = ret_fp; - ret_fp->rv->full_cb = (rbuffer_callback)&file_rb_write_full_cb; - } + ret_fp->buffer = alloc_block(); + ret_fp->read_pos = ret_fp->buffer; + ret_fp->write_pos = ret_fp->buffer; ret_fp->bytes_read = 0; return 0; } @@ -148,8 +145,9 @@ void file_open_buffer(FileDescriptor *ret_fp, char *data, size_t len) ret_fp->non_blocking = false; ret_fp->fd = -1; ret_fp->eof = true; - ret_fp->rv = rbuffer_new_wrap_buf(data, len); - ret_fp->_error = 0; + ret_fp->buffer = NULL; // we don't take ownership + ret_fp->read_pos = data; + ret_fp->write_pos = data + len; ret_fp->bytes_read = 0; } @@ -163,36 +161,18 @@ int file_close(FileDescriptor *const fp, const bool do_fsync) FUNC_ATTR_NONNULL_ALL { if (fp->fd < 0) { - rbuffer_free(fp->rv); return 0; } const int flush_error = (do_fsync ? file_fsync(fp) : file_flush(fp)); const int close_error = os_close(fp->fd); - rbuffer_free(fp->rv); + free_block(fp->buffer); if (close_error != 0) { return close_error; } return flush_error; } -/// Flush file modifications to disk -/// -/// @param[in,out] fp File to work with. -/// -/// @return 0 or error code. -int file_flush(FileDescriptor *const fp) - FUNC_ATTR_NONNULL_ALL -{ - if (!fp->wr) { - return 0; - } - file_rb_write_full_cb(fp->rv, fp); - 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. @@ -218,36 +198,29 @@ int file_fsync(FileDescriptor *const fp) return 0; } -/// Buffer used for writing -/// -/// Like IObuff, but allows file_\* callers not to care about spoiling it. -static char writebuf[kRWBufferSize]; - -/// Function run when RBuffer is full when writing to a file -/// -/// Actually does writing to the file, may also be invoked directly. +/// Flush file modifications to disk /// -/// @param[in,out] rv RBuffer instance used. /// @param[in,out] fp File to work with. -static void file_rb_write_full_cb(RBuffer *const rv, void *const fp_in) +/// +/// @return 0 or error code. +int file_flush(FileDescriptor *fp) FUNC_ATTR_NONNULL_ALL { - FileDescriptor *const fp = fp_in; - assert(fp->wr); - assert(rv->data == (void *)fp); - if (rbuffer_size(rv) == 0) { - return; + if (!fp->wr) { + return 0; + } + + ptrdiff_t to_write = fp->write_pos - fp->read_pos; + if (to_write == 0) { + return 0; } - const size_t read_bytes = rbuffer_read(rv, writebuf, kRWBufferSize); - const ptrdiff_t wres = os_write(fp->fd, writebuf, read_bytes, + const ptrdiff_t wres = os_write(fp->fd, fp->read_pos, (size_t)to_write, fp->non_blocking); - if (wres != (ptrdiff_t)read_bytes) { - if (wres >= 0) { - fp->_error = UV_EIO; - } else { - fp->_error = (int)wres; - } + fp->read_pos = fp->write_pos = fp->buffer; + if (wres != to_write) { + return (wres >= 0) ? UV_EIO : (int)wres; } + return 0; } /// Read from file @@ -262,77 +235,78 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, const size_t FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { assert(!fp->wr); - char *buf = ret_buf; - size_t read_remaining = size; - RBuffer *const rv = fp->rv; + size_t from_buffer = MIN((size_t)(fp->write_pos - fp->read_pos), size); + memcpy(ret_buf, fp->read_pos, from_buffer); + + char *buf = ret_buf + from_buffer; + size_t read_remaining = size - from_buffer; + if (!read_remaining) { + fp->bytes_read += from_buffer; + fp->read_pos += from_buffer; + return (ptrdiff_t)from_buffer; + } + + // at this point, we have consumed all of an existing buffer. restart from the beginning + fp->read_pos = fp->write_pos = fp->buffer; + +#ifdef HAVE_READV bool called_read = false; while (read_remaining) { - const size_t rv_size = rbuffer_size(rv); - if (rv_size > 0) { - const size_t rsize = rbuffer_read(rv, buf, MIN(rv_size, read_remaining)); - buf += rsize; - read_remaining -= rsize; - } - if (fp->eof - // Allow only at most one os_read[v] call. - || (called_read && fp->non_blocking)) { + // Allow only at most one os_read[v] call. + if (fp->eof || (called_read && fp->non_blocking)) { break; } - if (read_remaining) { - assert(rbuffer_size(rv) == 0); - rbuffer_reset(rv); -#ifdef HAVE_READV - // If there is readv() syscall, then take an opportunity to populate - // both target buffer and RBuffer at once, … - size_t write_count; - struct iovec iov[] = { - { .iov_base = buf, .iov_len = read_remaining }, - { .iov_base = rbuffer_write_ptr(rv, &write_count), - .iov_len = kRWBufferSize }, - }; - assert(write_count == kRWBufferSize); - const ptrdiff_t r_ret = os_readv(fp->fd, &fp->eof, iov, - ARRAY_SIZE(iov), fp->non_blocking); - if (r_ret > 0) { - if (r_ret > (ptrdiff_t)read_remaining) { - rbuffer_produced(rv, (size_t)(r_ret - (ptrdiff_t)read_remaining)); - read_remaining = 0; - } else { - buf += (size_t)r_ret; - read_remaining -= (size_t)r_ret; - } - } else if (r_ret < 0) { - return r_ret; - } -#else - if (read_remaining >= kRWBufferSize) { - // …otherwise leave RBuffer empty and populate only target buffer, - // because filtering information through rbuffer will be more syscalls. - const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, buf, read_remaining, - fp->non_blocking); - if (r_ret >= 0) { - read_remaining -= (size_t)r_ret; - fp->bytes_read += (size - read_remaining); - return (ptrdiff_t)(size - read_remaining); - } else if (r_ret < 0) { - return r_ret; - } + // If there is readv() syscall, then take an opportunity to populate + // both target buffer and RBuffer at once, … + struct iovec iov[] = { + { .iov_base = buf, .iov_len = read_remaining }, + { .iov_base = fp->write_pos, + .iov_len = ARENA_BLOCK_SIZE }, + }; + const ptrdiff_t r_ret = os_readv(fp->fd, &fp->eof, iov, + ARRAY_SIZE(iov), fp->non_blocking); + if (r_ret > 0) { + if (r_ret > (ptrdiff_t)read_remaining) { + fp->write_pos += (size_t)(r_ret - (ptrdiff_t)read_remaining); + read_remaining = 0; } else { - size_t write_count; - const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, - rbuffer_write_ptr(rv, &write_count), - kRWBufferSize, fp->non_blocking); - assert(write_count == kRWBufferSize); - if (r_ret > 0) { - rbuffer_produced(rv, (size_t)r_ret); - } else if (r_ret < 0) { - return r_ret; - } + buf += r_ret; + read_remaining -= (size_t)r_ret; } -#endif - called_read = true; + } else if (r_ret < 0) { + return r_ret; + } + called_read = true; + } +#else + if (fp->eof) { + // already eof, cannot read more + } else if (read_remaining >= ARENA_BLOCK_SIZE) { + // …otherwise leave fp->buffer empty and populate only target buffer, + // because filtering information through rbuffer will be more syscalls. + const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, buf, read_remaining, + fp->non_blocking); + if (r_ret >= 0) { + read_remaining -= (size_t)r_ret; + } else if (r_ret < 0) { + return r_ret; + } + } else { + const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, + fp->write_pos, + ARENA_BLOCK_SIZE, fp->non_blocking); + if (r_ret < 0) { + return r_ret; + } else { + fp->write_pos += r_ret; + size_t to_copy = MIN((size_t)r_ret, read_remaining); + memcpy(ret_buf, fp->read_pos, to_copy); + fp->read_pos += to_copy; + read_remaining -= to_copy; } } +#endif + fp->bytes_read += (size - read_remaining); return (ptrdiff_t)(size - read_remaining); } @@ -348,40 +322,68 @@ ptrdiff_t file_write(FileDescriptor *const fp, const char *const buf, const size FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1) { assert(fp->wr); - const size_t written = rbuffer_write(fp->rv, buf, size); - if (fp->_error != 0) { - const int error = fp->_error; - fp->_error = 0; - return error; - } else if (written != size) { - return UV_EIO; + ptrdiff_t space = (fp->buffer + ARENA_BLOCK_SIZE) - fp->write_pos; + // includes the trivial case of size==0 + if (size < (size_t)space) { + memcpy(fp->write_pos, buf, size); + fp->write_pos += size; + return (ptrdiff_t)size; + } + + // TODO(bfredl): just as for reading, use iovec to combine fp->buffer with buf + int status = file_flush(fp); + if (status < 0) { + return status; + } + + if (size < ARENA_BLOCK_SIZE) { + memcpy(fp->write_pos, buf, size); + fp->write_pos += size; + return (ptrdiff_t)size; } - return (ptrdiff_t)written; -} -/// Buffer used for skipping. Its contents is undefined and should never be -/// used. -static char skipbuf[kRWBufferSize]; + const ptrdiff_t wres = os_write(fp->fd, buf, size, + fp->non_blocking); + return (wres != (ptrdiff_t)size && wres >= 0) ? UV_EIO : wres; +} /// Skip some bytes /// /// This is like `fseek(fp, size, SEEK_CUR)`, but actual implementation simply -/// reads to a buffer and discards the result. +/// reads to the buffer and discards the result. ptrdiff_t file_skip(FileDescriptor *const fp, const size_t size) FUNC_ATTR_NONNULL_ALL { assert(!fp->wr); - size_t read_bytes = 0; - do { - const ptrdiff_t new_read_bytes = - file_read(fp, skipbuf, MIN(size - read_bytes, sizeof(skipbuf))); - if (new_read_bytes < 0) { - return new_read_bytes; - } else if (new_read_bytes == 0) { + size_t from_buffer = MIN((size_t)(fp->write_pos - fp->read_pos), size); + size_t skip_remaining = size - from_buffer; + if (skip_remaining == 0) { + fp->read_pos += from_buffer; + fp->bytes_read += from_buffer; + return (ptrdiff_t)from_buffer; + } + + fp->read_pos = fp->write_pos = fp->buffer; + bool called_read = false; + while (skip_remaining > 0) { + // Allow only at most one os_read[v] call. + if (fp->eof || (called_read && fp->non_blocking)) { break; } - read_bytes += (size_t)new_read_bytes; - } while (read_bytes < size && !file_eof(fp)); + const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, fp->buffer, ARENA_BLOCK_SIZE, + fp->non_blocking); + if (r_ret < 0) { + return r_ret; + } else if ((size_t)r_ret > skip_remaining) { + fp->read_pos = fp->buffer + skip_remaining; + fp->write_pos = fp->buffer + r_ret; + fp->bytes_read += size; + return (ptrdiff_t)size; + } + skip_remaining -= (size_t)r_ret; + called_read = true; + } - return (ptrdiff_t)read_bytes; + fp->bytes_read += size - skip_remaining; + return (ptrdiff_t)(size - skip_remaining); } diff --git a/src/nvim/os/fileio_defs.h b/src/nvim/os/fileio_defs.h index 10277d2a7a..63b6f51c17 100644 --- a/src/nvim/os/fileio_defs.h +++ b/src/nvim/os/fileio_defs.h @@ -8,9 +8,10 @@ /// Structure used to read from/write to file typedef struct { - int fd; ///< File descriptor. - int _error; ///< Error code for use with RBuffer callbacks or zero. - RBuffer *rv; ///< Read or write buffer. + int fd; ///< File descriptor. Can be -1 if no backing file (file_open_buffer) + char *buffer; ///< Read or write buffer. always ARENA_BLOCK_SIZE if allocated + char *read_pos; ///< read position in buffer + char *write_pos; ///< write position in buffer bool wr; ///< True if file is in write mode. bool eof; ///< True if end of file was encountered. bool non_blocking; ///< True if EAGAIN should not restart syscalls. @@ -28,7 +29,7 @@ static inline bool file_eof(const FileDescriptor *fp) /// performed. static inline bool file_eof(const FileDescriptor *const fp) { - return fp->eof && rbuffer_size(fp->rv) == 0; + return fp->eof && fp->read_pos == fp->write_pos; } static inline int file_fd(const FileDescriptor *fp) -- cgit From c13c50b752dca322a5ec77dea6188c9e3694549b Mon Sep 17 00:00:00 2001 From: bfredl Date: Thu, 30 May 2024 12:59:02 +0200 Subject: refactor(io): separate types for read and write streams This is a structural refactor with no logical changes, yet. Done in preparation for simplifying rstream/rbuffer which will require more state inline in RStream. The initial idea was to have RStream and WStream as sub-types symetrically but that doesn't work, as sockets are both reading and writing. Also there is very little write-specific state to start with, so the benefit of a separate WStream struct is a lot smaller. Just document what fields in `Stream` are write specific. --- src/nvim/os/input.c | 12 ++++++------ src/nvim/os/pty_process_unix.c | 6 +++--- src/nvim/os/pty_process_win.c | 8 ++++---- src/nvim/os/shell.c | 6 +++--- 4 files changed, 16 insertions(+), 16 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 60b5b48745..cfe8696cdd 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -41,7 +41,7 @@ typedef enum { kInputEof, } InbufPollResult; -static Stream read_stream = { .closed = true }; // Input before UI starts. +static RStream read_stream = { .s.closed = true }; // Input before UI starts. static RBuffer *input_buffer = NULL; static bool input_eof = false; static bool blocking = false; @@ -59,7 +59,7 @@ void input_init(void) void input_start(void) { - if (!read_stream.closed) { + if (!read_stream.s.closed) { return; } @@ -70,12 +70,12 @@ void input_start(void) void input_stop(void) { - if (read_stream.closed) { + if (read_stream.s.closed) { return; } rstream_stop(&read_stream); - stream_close(&read_stream, NULL, NULL); + rstream_may_close(&read_stream); } #ifdef EXITFREE @@ -138,7 +138,7 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e uint64_t wait_start = os_hrtime(); cursorhold_time = MIN(cursorhold_time, (int)p_ut); if ((result = inbuf_poll((int)p_ut - cursorhold_time, events)) == kInputNone) { - if (read_stream.closed && silent_mode) { + if (read_stream.s.closed && silent_mode) { // Drained eventloop & initial input; exit silent/batch-mode (-es/-Es). read_error_exit(); } @@ -489,7 +489,7 @@ bool input_available(void) return rbuffer_size(input_buffer) != 0; } -static void input_read_cb(Stream *stream, RBuffer *buf, size_t c, void *data, bool at_eof) +static void input_read_cb(RStream *stream, RBuffer *buf, size_t c, void *data, bool at_eof) { if (at_eof) { input_eof = true; diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index 4d34e8fac4..cfa4dcada7 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -169,7 +169,7 @@ int pty_process_spawn(PtyProcess *ptyproc) int status = 0; // zero or negative error code (libuv convention) Process *proc = (Process *)ptyproc; - assert(proc->err.closed); + assert(proc->err.s.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(); @@ -208,8 +208,8 @@ int pty_process_spawn(PtyProcess *ptyproc) && (status = set_duplicating_descriptor(master, &proc->in.uv.pipe))) { goto error; } - if (!proc->out.closed - && (status = set_duplicating_descriptor(master, &proc->out.uv.pipe))) { + if (!proc->out.s.closed + && (status = set_duplicating_descriptor(master, &proc->out.s.uv.pipe))) { goto error; } diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index 12831ff05f..f73baed490 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -55,7 +55,7 @@ int pty_process_spawn(PtyProcess *ptyproc) wchar_t *env = NULL; const char *emsg = NULL; - assert(proc->err.closed); + assert(proc->err.s.closed); if (!os_has_conpty_working() || (conpty_object = os_conpty_init(&in_name, &out_name, ptyproc->width, @@ -72,10 +72,10 @@ int pty_process_spawn(PtyProcess *ptyproc) pty_process_connect_cb); } - if (!proc->out.closed) { + if (!proc->out.s.closed) { out_req = xmalloc(sizeof(uv_connect_t)); uv_pipe_connect(out_req, - &proc->out.uv.pipe, + &proc->out.s.uv.pipe, out_name, pty_process_connect_cb); } @@ -216,7 +216,7 @@ static void wait_eof_timer_cb(uv_timer_t *wait_eof_timer) Process *proc = (Process *)ptyproc; assert(ptyproc->finish_wait != NULL); - if (proc->out.closed || proc->out.did_eof || !uv_is_readable(proc->out.uvstream)) { + if (proc->out.s.closed || proc->out.did_eof || !uv_is_readable(proc->out.s.uvstream)) { uv_timer_stop(&ptyproc->wait_eof_timer); pty_process_finish2(ptyproc); } diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 2a10510b0f..958faa4d22 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -987,7 +987,7 @@ static void dynamic_buffer_ensure(DynamicBuffer *buf, size_t desired) buf->data = xrealloc(buf->data, buf->cap); } -static void system_data_cb(Stream *stream, RBuffer *buf, size_t count, void *data, bool eof) +static void system_data_cb(RStream *stream, RBuffer *buf, size_t count, void *data, bool eof) { DynamicBuffer *dbuf = data; @@ -1151,7 +1151,7 @@ end: ui_flush(); } -static void out_data_cb(Stream *stream, RBuffer *buf, size_t count, void *data, bool eof) +static void out_data_cb(RStream *stream, RBuffer *buf, size_t count, void *data, bool eof) { size_t cnt; char *ptr = rbuffer_read_ptr(buf, &cnt); @@ -1331,7 +1331,7 @@ static void shell_write_cb(Stream *stream, void *data, int status) msg_schedule_semsg(_("E5677: Error writing input to shell-command: %s"), uv_err_name(status)); } - stream_close(stream, NULL, NULL); + stream_close(stream, NULL, NULL, false); } /// Applies 'shellxescape' (p_sxe) and 'shellxquote' (p_sxq) to a command. -- cgit From 6d6974eae685feeccac027287b4dee58730a7464 Mon Sep 17 00:00:00 2001 From: bfredl Date: Fri, 31 May 2024 20:36:16 +0200 Subject: refactor(input): don't use a ring for input Since paste data is handled via a separate channel, the data processed via `input_buffer` is typically just explicit keys as typed in by the user. Therefore it should be fine to use `memmove()` to always put the remaining data in front when refilling the buffer. --- src/nvim/os/input.c | 111 ++++++++++++++++++++++++++++------------------------ 1 file changed, 60 insertions(+), 51 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index cfe8696cdd..63eca0b6da 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -33,7 +33,7 @@ #include "nvim/state_defs.h" #define READ_BUFFER_SIZE 0xfff -#define INPUT_BUFFER_SIZE (READ_BUFFER_SIZE * 4) +#define INPUT_BUFFER_SIZE ((READ_BUFFER_SIZE * 4) + MAX_KEY_CODE_LEN) typedef enum { kInputNone, @@ -42,7 +42,10 @@ typedef enum { } InbufPollResult; static RStream read_stream = { .s.closed = true }; // Input before UI starts. -static RBuffer *input_buffer = NULL; +static char input_buffer[INPUT_BUFFER_SIZE]; +static char *input_read_pos = input_buffer; +static char *input_write_pos = input_buffer; + static bool input_eof = false; static bool blocking = false; static int cursorhold_time = 0; ///< time waiting for CursorHold event @@ -52,11 +55,6 @@ static int cursorhold_tb_change_cnt = 0; ///< tb_change_cnt when waiting starte # include "os/input.c.generated.h" #endif -void input_init(void) -{ - input_buffer = rbuffer_new(INPUT_BUFFER_SIZE + MAX_KEY_CODE_LEN); -} - void input_start(void) { if (!read_stream.s.closed) { @@ -78,13 +76,6 @@ void input_stop(void) rstream_may_close(&read_stream); } -#ifdef EXITFREE -void input_free_all_mem(void) -{ - rbuffer_free(input_buffer); -} -#endif - static void cursorhold_event(void **argv) { event_T event = State & MODE_INSERT ? EVENT_CURSORHOLDI : EVENT_CURSORHOLD; @@ -119,9 +110,12 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e restart_cursorhold_wait(tb_change_cnt); } - if (maxlen && rbuffer_size(input_buffer)) { + if (maxlen && input_available()) { restart_cursorhold_wait(tb_change_cnt); - return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen); + size_t to_read = MIN((size_t)maxlen, input_available()); + memcpy(buf, input_read_pos, to_read); + input_read_pos += to_read; + return (int)to_read; } // No risk of a UI flood, so disable CTRL-C "interrupt" behavior if it's mapped. @@ -161,11 +155,14 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e return 0; } - if (maxlen && rbuffer_size(input_buffer)) { + if (maxlen && input_available()) { restart_cursorhold_wait(tb_change_cnt); - // Safe to convert rbuffer_read to int, it will never overflow since we use - // relatively small buffers. - return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen); + // Safe to convert rbuffer_read to int, it will never overflow since + // INPUT_BUFFER_SIZE fits in an int + size_t to_read = MIN((size_t)maxlen, input_available()); + memcpy(buf, input_read_pos, to_read); + input_read_pos += to_read; + return (int)to_read; } // If there are events, return the keys directly @@ -247,11 +244,28 @@ bool os_isatty(int fd) return uv_guess_handle(fd) == UV_TTY; } -void input_enqueue_raw(String keys) +size_t input_available(void) +{ + return (size_t)(input_write_pos - input_read_pos); +} + +static size_t input_space(void) +{ + return (size_t)(input_buffer + INPUT_BUFFER_SIZE - input_write_pos); +} + +void input_enqueue_raw(const char *data, size_t size) { - if (keys.size > 0) { - rbuffer_write(input_buffer, keys.data, keys.size); + if (input_read_pos > input_buffer) { + size_t available = input_available(); + memmove(input_buffer, input_read_pos, available); + input_read_pos = input_buffer; + input_write_pos = input_buffer + available; } + + size_t to_write = MIN(size, input_space()); + memcpy(input_write_pos, data, to_write); + input_write_pos += to_write; } size_t input_enqueue(String keys) @@ -259,7 +273,7 @@ size_t input_enqueue(String keys) const char *ptr = keys.data; const char *end = ptr + keys.size; - while (rbuffer_space(input_buffer) >= 19 && ptr < end) { + while (input_space() >= 19 && ptr < end) { // A "" form occupies at least 1 characters, and produces up // to 19 characters (1 + 5 * 3 for the char and 3 for a modifier). // In the case of K_SPECIAL (0x80), 3 bytes are escaped and needed, @@ -272,7 +286,7 @@ size_t input_enqueue(String keys) if (new_size) { new_size = handle_mouse_event(&ptr, buf, new_size); - rbuffer_write(input_buffer, (char *)buf, new_size); + input_enqueue_raw((char *)buf, new_size); continue; } @@ -293,11 +307,11 @@ size_t input_enqueue(String keys) // copy the character, escaping K_SPECIAL if ((uint8_t)(*ptr) == K_SPECIAL) { - rbuffer_write(input_buffer, (char *)&(uint8_t){ K_SPECIAL }, 1); - rbuffer_write(input_buffer, (char *)&(uint8_t){ KS_SPECIAL }, 1); - rbuffer_write(input_buffer, (char *)&(uint8_t){ KE_FILLER }, 1); + input_enqueue_raw((char *)&(uint8_t){ K_SPECIAL }, 1); + input_enqueue_raw((char *)&(uint8_t){ KS_SPECIAL }, 1); + input_enqueue_raw((char *)&(uint8_t){ KE_FILLER }, 1); } else { - rbuffer_write(input_buffer, ptr, 1); + input_enqueue_raw(ptr, 1); } ptr++; } @@ -422,7 +436,7 @@ static unsigned handle_mouse_event(const char **ptr, uint8_t *buf, unsigned bufs return bufsize; } -size_t input_enqueue_mouse(int code, uint8_t modifier, int grid, int row, int col) +void input_enqueue_mouse(int code, uint8_t modifier, int grid, int row, int col) { modifier |= check_multiclick(code, grid, row, col); uint8_t buf[7]; @@ -442,8 +456,7 @@ size_t input_enqueue_mouse(int code, uint8_t modifier, int grid, int row, int co mouse_col = col; size_t written = 3 + (size_t)(p - buf); - rbuffer_write(input_buffer, (char *)buf, written); - return written; + input_enqueue_raw((char *)buf, written); } /// @return true if the main loop is blocked and waiting for input. @@ -484,20 +497,15 @@ static InbufPollResult inbuf_poll(int ms, MultiQueue *events) return input_eof ? kInputEof : kInputNone; } -bool input_available(void) -{ - return rbuffer_size(input_buffer) != 0; -} - static void input_read_cb(RStream *stream, RBuffer *buf, size_t c, void *data, bool at_eof) { if (at_eof) { input_eof = true; } - assert(rbuffer_space(input_buffer) >= rbuffer_size(buf)); + assert(input_space() >= rbuffer_size(buf)); RBUFFER_UNTIL_EMPTY(buf, ptr, len) { - (void)rbuffer_write(input_buffer, ptr, len); + input_enqueue_raw(ptr, len); rbuffer_consumed(buf, len); } } @@ -508,23 +516,24 @@ static void process_ctrl_c(void) return; } - size_t consume_count = 0; - RBUFFER_EACH_REVERSE(input_buffer, c, i) { - if ((uint8_t)c == Ctrl_C - || ((uint8_t)c == 'C' && i >= 3 - && (uint8_t)(*rbuffer_get(input_buffer, i - 3)) == K_SPECIAL - && (uint8_t)(*rbuffer_get(input_buffer, i - 2)) == KS_MODIFIER - && (uint8_t)(*rbuffer_get(input_buffer, i - 1)) == MOD_MASK_CTRL)) { - *rbuffer_get(input_buffer, i) = Ctrl_C; + size_t available = input_available(); + ssize_t i; + for (i = (ssize_t)available - 1; i >= 0; i--) { + uint8_t c = (uint8_t)input_read_pos[i]; + if (c == Ctrl_C + || (c == 'C' && i >= 3 + && (uint8_t)input_read_pos[i - 3] == K_SPECIAL + && (uint8_t)input_read_pos[i - 2] == KS_MODIFIER + && (uint8_t)input_read_pos[i - 1] == MOD_MASK_CTRL)) { + input_read_pos[i] = Ctrl_C; got_int = true; - consume_count = i; break; } } - if (got_int && consume_count) { + if (got_int && i > 0) { // Remove all unprocessed input (typeahead) before the CTRL-C. - rbuffer_consumed(input_buffer, consume_count); + input_read_pos += i; } } @@ -548,7 +557,7 @@ static int push_event_key(uint8_t *buf, int maxlen) bool os_input_ready(MultiQueue *events) { return (typebuf_was_filled // API call filled typeahead - || rbuffer_size(input_buffer) // Input buffer filled + || input_available() // Input buffer filled || pending_events(events)); // Events must be processed } -- cgit From bb6190bec5f18c1f9e2c1d29ef1f7cf7912ea625 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 1 Jun 2024 08:19:41 -0700 Subject: refactor: move shared messages to errors.h #26214 --- src/nvim/os/fs.c | 1 + src/nvim/os/shell.c | 1 + 2 files changed, 2 insertions(+) (limited to 'src/nvim/os') diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 19bdf30311..13e87a1ca5 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -17,6 +17,7 @@ #endif #include "auto/config.h" +#include "nvim/errors.h" #include "nvim/os/fs.h" #include "nvim/os/os_defs.h" diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 958faa4d22..d572e9b933 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -10,6 +10,7 @@ #include "nvim/ascii_defs.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" +#include "nvim/errors.h" #include "nvim/eval.h" #include "nvim/eval/typval_defs.h" #include "nvim/event/defs.h" -- cgit From 200e7ad1578619e78c664bd0c6be024168433412 Mon Sep 17 00:00:00 2001 From: James Tirta Halim Date: Mon, 3 Jun 2024 11:10:30 +0700 Subject: fixup: apply the change on more files --- src/nvim/os/env.c | 28 ++++++++++++++-------------- src/nvim/os/fs.c | 6 +++--- src/nvim/os/pty_process_win.c | 2 +- src/nvim/os/shell.c | 8 ++++---- src/nvim/os/stdpaths.c | 2 +- 5 files changed, 23 insertions(+), 23 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 5a79004c41..4689414559 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -63,13 +63,13 @@ const char *os_getenv(const char *name) FUNC_ATTR_NONNULL_ALL { char *e = NULL; - if (name[0] == '\0') { + if (name[0] == NUL) { return NULL; } int r = 0; if (map_has(cstr_t, &envmap, name) && !!(e = (char *)pmap_get(cstr_t)(&envmap, name))) { - if (e[0] != '\0') { + if (e[0] != NUL) { // Found non-empty cached env var. // NOTE: This risks incoherence if an in-process library changes the // environment without going through our os_setenv() wrapper. If @@ -85,11 +85,11 @@ const char *os_getenv(const char *name) if (r == UV_ENOBUFS) { e = xmalloc(size); r = uv_os_getenv(name, e, &size); - if (r != 0 || size == 0 || e[0] == '\0') { + if (r != 0 || size == 0 || e[0] == NUL) { XFREE_CLEAR(e); goto end; } - } else if (r != 0 || size == 0 || buf[0] == '\0') { + } else if (r != 0 || size == 0 || buf[0] == NUL) { e = NULL; goto end; } else { @@ -110,7 +110,7 @@ end: bool os_env_exists(const char *name) FUNC_ATTR_NONNULL_ALL { - if (name[0] == '\0') { + if (name[0] == NUL) { return false; } // Use a tiny buffer because we don't care about the value: if uv_os_getenv() @@ -134,14 +134,14 @@ bool os_env_exists(const char *name) int os_setenv(const char *name, const char *value, int overwrite) FUNC_ATTR_NONNULL_ALL { - if (name[0] == '\0') { + if (name[0] == NUL) { return -1; } #ifdef MSWIN if (!overwrite && os_getenv(name) != NULL) { return 0; } - if (value[0] == '\0') { + if (value[0] == NUL) { // Windows (Vim-compat): Empty string undefines the env var. return os_unsetenv(name); } @@ -174,7 +174,7 @@ int os_setenv(const char *name, const char *value, int overwrite) int os_unsetenv(const char *name) FUNC_ATTR_NONNULL_ALL { - if (name[0] == '\0') { + if (name[0] == NUL) { return -1; } pmap_del2(&envmap, name); @@ -366,7 +366,7 @@ void os_get_hostname(char *hostname, size_t size) struct utsname vutsname; if (uname(&vutsname) < 0) { - *hostname = '\0'; + *hostname = NUL; } else { xstrlcpy(hostname, vutsname.nodename, size); } @@ -374,12 +374,12 @@ void os_get_hostname(char *hostname, size_t size) wchar_t 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'; + *hostname = NUL; DWORD err = GetLastError(); semsg("GetComputerNameW failed: %d", err); return; } - host_utf16[host_wsize] = '\0'; + host_utf16[host_wsize] = NUL; char *host_utf8; int conversion_result = utf16_to_utf8(host_utf16, -1, &host_utf8); @@ -391,7 +391,7 @@ void os_get_hostname(char *hostname, size_t size) xfree(host_utf8); #else emsg("os_get_hostname failed: missing uname()"); - *hostname = '\0'; + *hostname = NUL; #endif } @@ -885,9 +885,9 @@ void vim_get_prefix_from_exepath(char *exe_name) // but c_grammar.lua does not recognize it (yet). xstrlcpy(exe_name, get_vim_var_str(VV_PROGPATH), MAXPATHL * sizeof(*exe_name)); char *path_end = path_tail_with_sep(exe_name); - *path_end = '\0'; // remove the trailing "nvim.exe" + *path_end = NUL; // remove the trailing "nvim.exe" path_end = path_tail(exe_name); - *path_end = '\0'; // remove the trailing "bin/" + *path_end = NUL; // remove the trailing "bin/" } /// Vim getenv() wrapper with special handling of $HOME, $VIM, $VIMRUNTIME, diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 13e87a1ca5..b681b2f832 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -302,7 +302,7 @@ static bool is_executable_ext(const char *name, char **abspath) char *nameext = strrchr(name, '.'); size_t nameext_len = nameext ? strlen(nameext) : 0; xstrlcpy(os_buf, name, sizeof(os_buf)); - char *buf_end = xstrchrnul(os_buf, '\0'); + char *buf_end = xstrchrnul(os_buf, NUL); const char *pathext = os_getenv("PATHEXT"); if (!pathext) { pathext = ".com;.exe;.bat;.cmd"; @@ -310,7 +310,7 @@ static bool is_executable_ext(const char *name, char **abspath) const char *ext = pathext; while (*ext) { // If $PATHEXT itself contains dot: - if (ext[0] == '.' && (ext[1] == '\0' || ext[1] == ENV_SEPCHAR)) { + if (ext[0] == '.' && (ext[1] == NUL || ext[1] == ENV_SEPCHAR)) { if (is_executable(name, abspath)) { return true; } @@ -436,7 +436,7 @@ FILE *os_fopen(const char *path, const char *flags) assert(flags != NULL && strlen(flags) > 0 && strlen(flags) <= 2); int iflags = 0; // Per table in fopen(3) manpage. - if (flags[1] == '\0' || flags[1] == 'b') { + if (flags[1] == NUL || flags[1] == 'b') { switch (flags[0]) { case 'r': iflags = O_RDONLY; diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index f73baed490..39c3966c1c 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -399,7 +399,7 @@ static int build_env_block(dict_T *denv, wchar_t **env_block) QUEUE_INSERT_TAIL(&env_q, &env_node->node); } - // Additional '\0' after the final entry + // Additional NUL after the final entry env_block_len++; *env_block = xmalloc(sizeof(**env_block) * env_block_len); diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index d572e9b933..026f14ebc8 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -648,13 +648,13 @@ char *shell_argv_to_str(char **const argv) p++; } if (n < maxsize) { - rv[n - 1] = '\0'; + rv[n - 1] = NUL; } else { // Command too long, show ellipsis: "/bin/bash 'foo' 'bar'..." rv[maxsize - 4] = '.'; rv[maxsize - 3] = '.'; rv[maxsize - 2] = '.'; - rv[maxsize - 1] = '\0'; + rv[maxsize - 1] = NUL; } return rv; } @@ -861,7 +861,7 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu { 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'); + bool has_input = (input != NULL && input[0] != NUL); // the output buffer DynamicBuffer buf = DYNAMIC_BUFFER_INIT; @@ -1024,7 +1024,7 @@ static bool out_data_decide_throttle(size_t size) static uint64_t started = 0; // Start time of the current throttle. static size_t received = 0; // Bytes observed since last throttle. static size_t visit = 0; // "Pulse" count of the current throttle. - static char pulse_msg[] = { ' ', ' ', ' ', '\0' }; + static char pulse_msg[] = { ' ', ' ', ' ', NUL }; if (!size) { bool previous_decision = (visit > 0); diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c index e5bdd56fe6..e4435bcaa8 100644 --- a/src/nvim/os/stdpaths.c +++ b/src/nvim/os/stdpaths.c @@ -69,7 +69,7 @@ static const char *const xdg_defaults[] = { const char *get_appname(void) { const char *env_val = os_getenv("NVIM_APPNAME"); - if (env_val == NULL || *env_val == '\0') { + if (env_val == NULL || *env_val == NUL) { env_val = "nvim"; } return env_val; -- cgit From 84ad95fdc9d437fee711b5b8ac0acb6c1c950685 Mon Sep 17 00:00:00 2001 From: bfredl Date: Thu, 6 Jun 2024 13:48:05 +0200 Subject: fix(fileio): copy to correct buffer position when reading fixes #29186 (likely) fixup for #29093 064483a2b --- src/nvim/os/fileio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index c87b2d359f..5f372b2376 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -300,7 +300,7 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, const size_t } else { fp->write_pos += r_ret; size_t to_copy = MIN((size_t)r_ret, read_remaining); - memcpy(ret_buf, fp->read_pos, to_copy); + memcpy(buf, fp->read_pos, to_copy); fp->read_pos += to_copy; read_remaining -= to_copy; } -- cgit From 78d21593a35cf89692224f1000a04d3c9fff8add Mon Sep 17 00:00:00 2001 From: bfredl Date: Fri, 31 May 2024 14:40:53 +0200 Subject: refactor(io): make rstream use a linear buffer If you like it you shouldn't put a ring on it. This is what _every_ consumer of RStream used anyway, either by calling rbuffer_reset, or rbuffer_consumed_compact (same as rbuffer_reset without needing a scratch buffer), or by consuming everything in each stream_read_cb call directly. --- src/nvim/os/fileio.c | 2 -- src/nvim/os/fileio_defs.h | 1 - src/nvim/os/input.c | 16 ++++++---------- src/nvim/os/shell.c | 45 +++++++++++++++++---------------------------- 4 files changed, 23 insertions(+), 41 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index 5f372b2376..585c4964e2 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -21,8 +21,6 @@ #include "nvim/os/fileio.h" #include "nvim/os/fs.h" #include "nvim/os/os_defs.h" -#include "nvim/rbuffer.h" -#include "nvim/rbuffer_defs.h" #include "nvim/types_defs.h" #ifdef HAVE_SYS_UIO_H diff --git a/src/nvim/os/fileio_defs.h b/src/nvim/os/fileio_defs.h index 63b6f51c17..0f76fdb2aa 100644 --- a/src/nvim/os/fileio_defs.h +++ b/src/nvim/os/fileio_defs.h @@ -4,7 +4,6 @@ #include #include "nvim/func_attr.h" -#include "nvim/rbuffer_defs.h" /// Structure used to read from/write to file typedef struct { diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 63eca0b6da..ea21a32230 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -27,8 +27,6 @@ #include "nvim/os/os_defs.h" #include "nvim/os/time.h" #include "nvim/profile.h" -#include "nvim/rbuffer.h" -#include "nvim/rbuffer_defs.h" #include "nvim/state.h" #include "nvim/state_defs.h" @@ -62,7 +60,7 @@ void input_start(void) } used_stdin = true; - rstream_init_fd(&main_loop, &read_stream, STDIN_FILENO, READ_BUFFER_SIZE); + rstream_init_fd(&main_loop, &read_stream, STDIN_FILENO); rstream_start(&read_stream, input_read_cb, NULL); } @@ -157,7 +155,7 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e if (maxlen && input_available()) { restart_cursorhold_wait(tb_change_cnt); - // Safe to convert rbuffer_read to int, it will never overflow since + // Safe to convert `to_read` to int, it will never overflow since // INPUT_BUFFER_SIZE fits in an int size_t to_read = MIN((size_t)maxlen, input_available()); memcpy(buf, input_read_pos, to_read); @@ -497,17 +495,15 @@ static InbufPollResult inbuf_poll(int ms, MultiQueue *events) return input_eof ? kInputEof : kInputNone; } -static void input_read_cb(RStream *stream, RBuffer *buf, size_t c, void *data, bool at_eof) +static size_t input_read_cb(RStream *stream, const char *buf, size_t c, void *data, bool at_eof) { if (at_eof) { input_eof = true; } - assert(input_space() >= rbuffer_size(buf)); - RBUFFER_UNTIL_EMPTY(buf, ptr, len) { - input_enqueue_raw(ptr, len); - rbuffer_consumed(buf, len); - } + assert(input_space() >= c); + input_enqueue_raw(buf, c); + return c; } static void process_ctrl_c(void) diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 026f14ebc8..ee9b6ec68c 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -40,8 +40,6 @@ #include "nvim/path.h" #include "nvim/pos_defs.h" #include "nvim/profile.h" -#include "nvim/rbuffer.h" -#include "nvim/rbuffer_defs.h" #include "nvim/state_defs.h" #include "nvim/strings.h" #include "nvim/tag.h" @@ -907,9 +905,9 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu if (has_input) { wstream_init(&proc->in, 0); } - rstream_init(&proc->out, 0); + rstream_init(&proc->out); rstream_start(&proc->out, data_cb, &buf); - rstream_init(&proc->err, 0); + rstream_init(&proc->err); rstream_start(&proc->err, data_cb, &buf); // write the input, if any @@ -988,14 +986,14 @@ static void dynamic_buffer_ensure(DynamicBuffer *buf, size_t desired) buf->data = xrealloc(buf->data, buf->cap); } -static void system_data_cb(RStream *stream, RBuffer *buf, size_t count, void *data, bool eof) +static size_t system_data_cb(RStream *stream, const char *buf, size_t count, void *data, bool eof) { DynamicBuffer *dbuf = data; - size_t nread = buf->size; - dynamic_buffer_ensure(dbuf, dbuf->len + nread + 1); - rbuffer_read(buf, dbuf->data + dbuf->len, nread); - dbuf->len += nread; + dynamic_buffer_ensure(dbuf, dbuf->len + count + 1); + memcpy(dbuf->data + dbuf->len, buf, count); + dbuf->len += count; + return count; } /// Tracks output received for the current executing shell command, and displays @@ -1078,7 +1076,7 @@ static bool out_data_decide_throttle(size_t size) /// /// @param output Data to save, or NULL to invoke a special mode. /// @param size Length of `output`. -static void out_data_ring(char *output, size_t size) +static void out_data_ring(const char *output, size_t size) { #define MAX_CHUNK_SIZE (OUT_DATA_THRESHOLD / 2) static char last_skipped[MAX_CHUNK_SIZE]; // Saved output. @@ -1120,11 +1118,11 @@ static void out_data_ring(char *output, size_t size) /// @param output Data to append to screen lines. /// @param count Size of data. /// @param eof If true, there will be no more data output. -static void out_data_append_to_screen(char *output, size_t *count, bool eof) +static void out_data_append_to_screen(const char *output, size_t *count, bool eof) FUNC_ATTR_NONNULL_ALL { - char *p = output; - char *end = output + *count; + const char *p = output; + const char *end = output + *count; while (p < end) { if (*p == '\n' || *p == '\r' || *p == TAB || *p == BELL) { msg_putchar_attr((uint8_t)(*p), 0); @@ -1152,25 +1150,16 @@ end: ui_flush(); } -static void out_data_cb(RStream *stream, RBuffer *buf, size_t count, void *data, bool eof) +static size_t out_data_cb(RStream *stream, const char *ptr, size_t count, void *data, bool eof) { - size_t cnt; - char *ptr = rbuffer_read_ptr(buf, &cnt); - - if (ptr != NULL && cnt > 0 - && out_data_decide_throttle(cnt)) { // Skip output above a threshold. + if (count > 0 && out_data_decide_throttle(count)) { // 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 if (ptr != NULL) { - out_data_append_to_screen(ptr, &cnt, eof); - } - - if (cnt) { - rbuffer_consumed(buf, cnt); + out_data_ring(ptr, count); + } else if (count > 0) { + out_data_append_to_screen(ptr, &count, eof); } - // Move remaining data to start of buffer, so the buffer can never wrap around. - rbuffer_reset(buf); + return count; } /// Parses a command string into a sequence of words, taking quotes into -- cgit From caa2e842a1d67972210824aa5758c6d50f46ca52 Mon Sep 17 00:00:00 2001 From: bfredl Date: Sun, 2 Jun 2024 14:21:37 +0200 Subject: refactor(os/shell): we have DynamicBuffer at home MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DynamicBuffer at home: KVÄCK --- src/nvim/os/shell.c | 63 +++++++++++++++-------------------------------------- 1 file changed, 17 insertions(+), 46 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index ee9b6ec68c..b66faa2285 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -47,17 +47,11 @@ #include "nvim/ui.h" #include "nvim/vim_defs.h" -#define DYNAMIC_BUFFER_INIT { NULL, 0, 0 } #define NS_1_SECOND 1000000000U // 1 second, in nanoseconds #define OUT_DATA_THRESHOLD 1024 * 10U // 10KB, "a few screenfuls" of data. #define SHELL_SPECIAL "\t \"&'$;<>()\\|" -typedef struct { - char *data; - size_t cap, len; -} DynamicBuffer; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/shell.c.generated.h" #endif @@ -667,7 +661,7 @@ char *shell_argv_to_str(char **const argv) /// @return shell command exit code int os_call_shell(char *cmd, ShellOpts opts, char *extra_args) { - DynamicBuffer input = DYNAMIC_BUFFER_INIT; + StringBuilder input = KV_INITIAL_VALUE; char *output = NULL; char **output_ptr = NULL; int current_state = State; @@ -696,9 +690,9 @@ int os_call_shell(char *cmd, ShellOpts opts, char *extra_args) size_t nread; int exitcode = do_os_system(shell_build_argv(cmd, extra_args), - input.data, input.len, output_ptr, &nread, + input.items, input.size, output_ptr, &nread, emsg_silent, forward_output); - xfree(input.data); + kv_destroy(input); if (output) { write_output(output, nread, true); @@ -862,7 +856,7 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu bool has_input = (input != NULL && input[0] != NUL); // the output buffer - DynamicBuffer buf = DYNAMIC_BUFFER_INIT; + StringBuilder buf = KV_INITIAL_VALUE; stream_read_cb data_cb = system_data_cb; if (nread) { *nread = 0; @@ -950,18 +944,17 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu // prepare the out parameters if requested if (output) { - if (buf.len == 0) { + assert(nread); + if (buf.size == 0) { // no data received from the process, return NULL *output = NULL; - xfree(buf.data); + *nread = 0; + kv_destroy(buf); } else { + *nread = buf.size; // NUL-terminate to make the output directly usable as a C string - buf.data[buf.len] = NUL; - *output = buf.data; - } - - if (nread) { - *nread = buf.len; + kv_push(buf, NUL); + *output = buf.items; } } @@ -971,28 +964,10 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu return exitcode; } -/// - ensures at least `desired` bytes in buffer -/// -/// TODO(aktau): fold with kvec/garray -static void dynamic_buffer_ensure(DynamicBuffer *buf, size_t desired) -{ - if (buf->cap >= desired) { - assert(buf->data); - return; - } - - buf->cap = desired; - kv_roundup32(buf->cap); - buf->data = xrealloc(buf->data, buf->cap); -} - static size_t system_data_cb(RStream *stream, const char *buf, size_t count, void *data, bool eof) { - DynamicBuffer *dbuf = data; - - dynamic_buffer_ensure(dbuf, dbuf->len + count + 1); - memcpy(dbuf->data + dbuf->len, buf, count); - dbuf->len += count; + StringBuilder *dbuf = data; + kv_concat_len(*dbuf, buf, count); return count; } @@ -1223,7 +1198,7 @@ static size_t word_length(const char *str) /// event loop starts. If we don't (by writing in chunks returned by `ml_get`) /// the buffer being modified might get modified by reading from the process /// before we finish writing. -static void read_input(DynamicBuffer *buf) +static void read_input(StringBuilder *buf) { size_t written = 0; size_t len = 0; @@ -1237,14 +1212,11 @@ static void read_input(DynamicBuffer *buf) } else if (lp[written] == NL) { // NL -> NUL translation len = 1; - dynamic_buffer_ensure(buf, buf->len + len); - buf->data[buf->len++] = NUL; + kv_push(*buf, NUL); } else { char *s = vim_strchr(lp + written, NL); len = s == NULL ? l : (size_t)(s - (lp + written)); - dynamic_buffer_ensure(buf, buf->len + len); - memcpy(buf->data + buf->len, lp + written, len); - buf->len += len; + kv_concat_len(*buf, lp + written, len); } if (len == l) { @@ -1253,8 +1225,7 @@ static void read_input(DynamicBuffer *buf) || (!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))) { - dynamic_buffer_ensure(buf, buf->len + 1); - buf->data[buf->len++] = NL; + kv_push(*buf, NL); } lnum++; if (lnum > curbuf->b_op_end.lnum) { -- cgit From 19052e0a06240be018a234d87f51113eca6d17fa Mon Sep 17 00:00:00 2001 From: bfredl Date: Sat, 8 Jun 2024 14:19:30 +0200 Subject: refactor(shada): use msgpack_sbuffer less Work towards getting rid of libmsgpack depedency eventually. msgpack_sbuffer is just a string buffer, we can use our own String type. --- src/nvim/os/fileio.c | 3 +-- src/nvim/os/fileio.h | 6 ++++++ 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index 585c4964e2..89834bed80 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -320,9 +320,8 @@ ptrdiff_t file_write(FileDescriptor *const fp, const char *const buf, const size FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1) { assert(fp->wr); - ptrdiff_t space = (fp->buffer + ARENA_BLOCK_SIZE) - fp->write_pos; // includes the trivial case of size==0 - if (size < (size_t)space) { + if (size < file_space(fp)) { memcpy(fp->write_pos, buf, size); fp->write_pos += size; return (ptrdiff_t)size; diff --git a/src/nvim/os/fileio.h b/src/nvim/os/fileio.h index e8fd2209db..523f9657a4 100644 --- a/src/nvim/os/fileio.h +++ b/src/nvim/os/fileio.h @@ -2,6 +2,7 @@ #include // IWYU pragma: keep +#include "nvim/memory_defs.h" #include "nvim/os/fileio_defs.h" // IWYU pragma: keep /// file_open() flags @@ -32,6 +33,11 @@ enum { kRWBufferSize = 1024, }; +static inline size_t file_space(FileDescriptor *fp) +{ + return (size_t)((fp->buffer + ARENA_BLOCK_SIZE) - fp->write_pos); +} + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/fileio.h.generated.h" #endif -- cgit From bbd2f340a2895ed59785f952b2585e6590602cad Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 11 Jun 2024 10:25:32 +0200 Subject: refactor(memory): use builtin strcat() instead of STRCAT() The latter was mostly relevant with the past char_u madness. NOTE: STRCAT also functioned as a counterfeit "NOLINT" for clint apparently. But NOLINT-ing every usecase is just the same as disabling the check entirely. --- src/nvim/os/shell.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index b66faa2285..4b34ed1c4e 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -248,11 +248,11 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in } else { STRCPY(command, "("); } - STRCAT(command, pat[0] + 1); // exclude first backtick + strcat(command, pat[0] + 1); // exclude first backtick p = command + strlen(command) - 1; if (is_fish_shell) { *p-- = ';'; - STRCAT(command, " end"); + strcat(command, " end"); } else { *p-- = ')'; // remove last backtick } @@ -263,7 +263,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in ampersand = true; *p = ' '; } - STRCAT(command, ">"); + strcat(command, ">"); } else { STRCPY(command, ""); if (shell_style == STYLE_GLOB) { @@ -271,26 +271,26 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in // otherwise, this may set the positional parameters for the shell, // e.g. "$*". if (flags & EW_NOTFOUND) { - STRCAT(command, "set nonomatch; "); + strcat(command, "set nonomatch; "); } else { - STRCAT(command, "unset nonomatch; "); + strcat(command, "unset nonomatch; "); } } if (shell_style == STYLE_GLOB) { - STRCAT(command, "glob >"); + strcat(command, "glob >"); } else if (shell_style == STYLE_PRINT) { - STRCAT(command, "print -N >"); + strcat(command, "print -N >"); } else if (shell_style == STYLE_VIMGLOB) { - STRCAT(command, sh_vimglob_func); + strcat(command, sh_vimglob_func); } else if (shell_style == STYLE_GLOBSTAR) { - STRCAT(command, sh_globstar_opt); - STRCAT(command, sh_vimglob_func); + strcat(command, sh_globstar_opt); + strcat(command, sh_vimglob_func); } else { - STRCAT(command, "echo >"); + strcat(command, "echo >"); } } - STRCAT(command, tempname); + strcat(command, tempname); if (shell_style != STYLE_BT) { for (i = 0; i < num_pat; i++) { @@ -334,7 +334,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in } if (ampersand) { - STRCAT(command, "&"); // put the '&' after the redirection + strcat(command, "&"); // put the '&' after the redirection } // Using zsh -G: If a pattern has no matches, it is just deleted from -- cgit From 1a1c766049826b6049610edda8f72eac1f75b38d Mon Sep 17 00:00:00 2001 From: Rafael Kitover Date: Tue, 18 Jun 2024 03:23:52 +0000 Subject: refactor: Windows tilde expansion followup (#29380) Followup to #28515: Rename the static os_homedir() to os_uv_homedir() to emphasize that it is a wrapper around a libuv function. Add the function os_get_homedir() to os/env.c to return the cached homedir value as a const. Must be called after homedir is initialized or it fails. The difference between this function and the static os_uv_homedir() is that the latter gets the homedir from libuv and is used to initialize homedir in init_homedir(), while os_get_homedir() just returns homedir as a const if it's initialized and is public. Use the os_get_homedir() accessor for ~/ expansion on Windows to make the code more concise. Add a Windows section to main_spec.lua with tests for expanding ~/ and ~\ prefixes for files passed in on the command-line. Signed-off-by: Rafael Kitover --- src/nvim/os/env.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 4689414559..a4d5c02b5b 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -400,17 +400,28 @@ void os_get_hostname(char *hostname, size_t size) /// 2. if $HOME is not set, try the following /// For Windows: /// 1. assemble homedir using HOMEDRIVE and HOMEPATH -/// 2. try os_homedir() +/// 2. try os_uv_homedir() /// 3. resolve a direct reference to another system variable /// 4. guess C drive /// For Unix: -/// 1. try os_homedir() +/// 1. try os_uv_homedir() /// 2. go to that directory /// This also works with mounts and links. /// Don't do this for Windows, it will change the "current dir" for a drive. /// 3. fall back to current working directory as a last resort static char *homedir = NULL; -static char *os_homedir(void); +static char *os_uv_homedir(void); + +/// Public accessor for the cached "real", resolved user home directory. See +/// comment on `homedir`. +const char *os_get_homedir(void) +{ + if (!homedir) { + emsg("os_get_homedir failed: homedir not initialized"); + return NULL; + } + return homedir; +} void init_homedir(void) { @@ -440,7 +451,7 @@ void init_homedir(void) } } if (var == NULL) { - var = os_homedir(); + var = os_uv_homedir(); } // Weird but true: $HOME may contain an indirect reference to another @@ -471,7 +482,7 @@ void init_homedir(void) #ifdef UNIX if (var == NULL) { - var = os_homedir(); + var = os_uv_homedir(); } // Get the actual path. This resolves links. @@ -492,7 +503,7 @@ void init_homedir(void) static char homedir_buf[MAXPATHL]; -static char *os_homedir(void) +static char *os_uv_homedir(void) { homedir_buf[0] = NUL; size_t homedir_size = MAXPATHL; -- cgit From 7dffc36e61c46e6adc92cff5944e876446f3c40e Mon Sep 17 00:00:00 2001 From: bfredl Date: Thu, 13 Jun 2024 12:00:58 +0200 Subject: refactor(declarations): also generate prototypes for functions in headers Before this change, "static inline" functions in headers needed to have their function attributes specified in a completely different way. The prototype had to be duplicated, and REAL_FATTR_ had to be used instead of the public FUNC_ATTR_ names. TODO: need a check that a "header.h.inline.generated.h" file is not forgotten when the first "static inline" function with attributes is added to a header (they would just be silently missing). --- src/nvim/os/fileio_defs.h | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/fileio_defs.h b/src/nvim/os/fileio_defs.h index 0f76fdb2aa..47f0629ccf 100644 --- a/src/nvim/os/fileio_defs.h +++ b/src/nvim/os/fileio_defs.h @@ -3,8 +3,6 @@ #include #include -#include "nvim/func_attr.h" - /// Structure used to read from/write to file typedef struct { int fd; ///< File descriptor. Can be -1 if no backing file (file_open_buffer) @@ -17,8 +15,9 @@ typedef struct { uint64_t bytes_read; ///< total bytes read so far } FileDescriptor; -static inline bool file_eof(const FileDescriptor *fp) - REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/fileio_defs.h.inline.generated.h" +#endif /// Check whether end of file was encountered /// @@ -27,19 +26,18 @@ static inline bool file_eof(const FileDescriptor *fp) /// @return true if it was, false if it was not or read operation was never /// performed. static inline bool file_eof(const FileDescriptor *const fp) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { return fp->eof && fp->read_pos == fp->write_pos; } -static inline int file_fd(const FileDescriptor *fp) - REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; - /// Return the file descriptor associated with the FileDescriptor structure /// /// @param[in] fp File to check. /// /// @return File descriptor. static inline int file_fd(const FileDescriptor *const fp) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { return fp->fd; } -- cgit From f926cc32c9262b6254e2843276b951cef9da1afe Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 2 Jul 2024 13:45:50 +0200 Subject: refactor(shada): rework msgpack decoding without msgpack-c This also makes shada reading slightly faster due to avoiding some copying and allocation. Use keysets to drive decoding of msgpack maps for shada entries. --- src/nvim/os/fileio.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'src/nvim/os') diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index 89834bed80..1981d0dfd4 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -309,6 +309,22 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, const size_t return (ptrdiff_t)(size - read_remaining); } +/// try to read already buffered data in place +/// +/// @return NULL if enough data is not available +/// valid pointer to chunk of "size". pointer becomes invalid in the next "file_read" call! +char *file_try_read_buffered(FileDescriptor *const fp, const size_t size) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + if ((size_t)(fp->write_pos - fp->read_pos) >= size) { + char *ret = fp->read_pos; + fp->read_pos += size; + fp->bytes_read += size; + return ret; + } + return NULL; +} + /// Write to a file /// /// @param[in] fd File descriptor to write to. -- cgit From 336ab2682e08b95c51b8cbafcf9697b5a42026d1 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 9 Aug 2024 06:57:17 +0800 Subject: vim-patch:8.2.4860: MS-Windows: always uses current directory for executables Problem: MS-Windows: always uses current directory for executables. Solution: Check the NoDefaultCurrentDirectoryInExePath environment variable. (Yasuhiro Matsumoto, closes vim/vim#10341) https://github.com/vim/vim/commit/05cf63e9bdca1ac070df3e7d9c6dfc45e68ac916 Omit doc change: override in later doc update. Co-authored-by: Yasuhiro Matsumoto --- src/nvim/os/fs.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index b681b2f832..d0da37b8e7 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -17,7 +17,6 @@ #endif #include "auto/config.h" -#include "nvim/errors.h" #include "nvim/os/fs.h" #include "nvim/os/os_defs.h" @@ -36,6 +35,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/ascii_defs.h" +#include "nvim/errors.h" #include "nvim/gettext_defs.h" #include "nvim/globals.h" #include "nvim/log.h" @@ -356,10 +356,16 @@ static bool is_executable_in_path(const char *name, char **abspath) } #ifdef MSWIN - // Prepend ".;" to $PATH. - size_t pathlen = strlen(path_env); - char *path = memcpy(xmallocz(pathlen + 2), "." ENV_SEPSTR, 2); - memcpy(path + 2, path_env, pathlen); + char *path = NULL; + if (!os_env_exists("NoDefaultCurrentDirectoryInExePath")) { + // Prepend ".;" to $PATH. + size_t pathlen = strlen(path_env); + path = xmallocz(pathlen + 2); + memcpy(path, "." ENV_SEPSTR, 2); + memcpy(path + 2, path_env, pathlen); + } else { + path = xstrdup(path_env); + } #else char *path = xstrdup(path_env); #endif -- cgit From fa79a8ad6deefeea81c1959d69aa4c8b2d993f99 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Thu, 8 Aug 2024 12:28:47 +0200 Subject: build(deps): vendor libvterm at v0.3.3 Problem: Adding support for modern Nvim features (reflow, OSC 8, full utf8/emoji support) requires coupling libvterm to Nvim internals (e.g., utf8proc). Solution: Vendor libvterm at v0.3.3. --- src/nvim/os/win_defs.h | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/nvim/os') diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h index 852059f78b..024719806c 100644 --- a/src/nvim/os/win_defs.h +++ b/src/nvim/os/win_defs.h @@ -16,6 +16,9 @@ #include #include +// vterm.h defines an `unsigned int small` in a struct, triggering error C2632 +#undef small + // Windows does not have S_IFLNK but libuv defines it // and sets the flag for us when calling uv_fs_stat. #include -- cgit From cd05a72fec49bfaa3911c141ac605b67b6e2270a Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Thu, 29 Aug 2024 00:11:32 +0200 Subject: docs: misc (#29719) Co-authored-by: Evgeni Chasnovski Co-authored-by: Lauri Heiskanen Co-authored-by: Piotr Doroszewski <5605596+Doroszewski@users.noreply.github.com> Co-authored-by: Tobiasz Laskowski Co-authored-by: ariel-lindemann <41641978+ariel-lindemann@users.noreply.github.com> Co-authored-by: glepnir Co-authored-by: zeertzjq --- src/nvim/os/input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index ea21a32230..c4eb2803f6 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -564,7 +564,7 @@ static void read_error_exit(void) if (silent_mode) { // Normal way to exit for "nvim -es". getout(0); } - preserve_exit(_("Vim: Error reading input, exiting...\n")); + preserve_exit(_("Nvim: Error reading input, exiting...\n")); } static bool pending_events(MultiQueue *events) -- cgit From 61e9137394fc5229e582a64316c2ffef55d8d7af Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 1 Sep 2024 13:01:24 -0700 Subject: docs: misc #28970 --- src/nvim/os/env.c | 30 +++++++++++++++--------------- src/nvim/os/input.c | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index a4d5c02b5b..8dfedd073e 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -395,7 +395,21 @@ void os_get_hostname(char *hostname, size_t size) #endif } -/// To get the "real" home directory: +/// The "real" home directory as determined by `init_homedir`. +static char *homedir = NULL; +static char *os_uv_homedir(void); + +/// Gets the "real", resolved user home directory as determined by `init_homedir`. +const char *os_homedir(void) +{ + if (!homedir) { + emsg("os_homedir failed: homedir not initialized"); + return NULL; + } + return homedir; +} + +/// Sets `homedir` to the "real", resolved user home directory, as follows: /// 1. get value of $HOME /// 2. if $HOME is not set, try the following /// For Windows: @@ -409,20 +423,6 @@ void os_get_hostname(char *hostname, size_t size) /// This also works with mounts and links. /// Don't do this for Windows, it will change the "current dir" for a drive. /// 3. fall back to current working directory as a last resort -static char *homedir = NULL; -static char *os_uv_homedir(void); - -/// Public accessor for the cached "real", resolved user home directory. See -/// comment on `homedir`. -const char *os_get_homedir(void) -{ - if (!homedir) { - emsg("os_get_homedir failed: homedir not initialized"); - return NULL; - } - return homedir; -} - void init_homedir(void) { // In case we are called a second time. diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index c4eb2803f6..8affc58591 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -514,7 +514,7 @@ static void process_ctrl_c(void) size_t available = input_available(); ssize_t i; - for (i = (ssize_t)available - 1; i >= 0; i--) { + for (i = (ssize_t)available - 1; i >= 0; i--) { // Reverse-search input for Ctrl_C. uint8_t c = (uint8_t)input_read_pos[i]; if (c == Ctrl_C || (c == 'C' && i >= 3 -- cgit From f9108378b7a7e08b48685f0a3ff4f7a3a14b56d6 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Wed, 14 Aug 2024 15:52:51 +0200 Subject: refactor: adopt termkey and eliminate duplicate code Termkey is abandoned and it's now our code, so there's no reason not to treat it as such. An alternative approach could be to have a proper repo that we maintain such as with unibilium, although with this approach we can make a few assumptions that will allow us to remove more code. Also eliminate duplicate code from both termkey and libvterm. --- src/nvim/os/win_defs.h | 3 --- 1 file changed, 3 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h index 024719806c..852059f78b 100644 --- a/src/nvim/os/win_defs.h +++ b/src/nvim/os/win_defs.h @@ -16,9 +16,6 @@ #include #include -// vterm.h defines an `unsigned int small` in a struct, triggering error C2632 -#undef small - // Windows does not have S_IFLNK but libuv defines it // and sets the flag for us when calling uv_fs_stat. #include -- cgit From 8a2aec99748229ad9d1e12c1cbc0768d063e8eed Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 8 Sep 2024 12:48:32 -0700 Subject: fix(startup): server fails if $NVIM_APPNAME is relative dir #30310 Problem: If $NVIM_APPNAME is a relative dir path, Nvim fails to start its primary/default server, and `v:servername` is empty. Root cause is d34c64e342dfba9248d1055e702d02620a1b31a8, but this wasn't noticed until 96128a5076b7 started reporting the error more loudly. Solution: - `server_address_new`: replace slashes "/" in the appname before using it as a servername. - `vim_mktempdir`: always prefer the system-wide top-level "nvim.user/" directory. That isn't intended to be specific to NVIM_APPNAME; rather, each *subdirectory* ("nvim.user/xxx") is owned by each Nvim instance. Nvim "apps" can be identified by the server socket(s) stored in those per-Nvim subdirs. fix #30256 --- src/nvim/os/stdpaths.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c index e4435bcaa8..e9a74d197f 100644 --- a/src/nvim/os/stdpaths.c +++ b/src/nvim/os/stdpaths.c @@ -63,22 +63,32 @@ static const char *const xdg_defaults[] = { #endif }; -/// Get the value of $NVIM_APPNAME or "nvim" if not set. +/// Gets the value of $NVIM_APPNAME, or "nvim" if not set. +/// +/// @param namelike Write "name-like" value (no path separators) in `NameBuff`. /// /// @return $NVIM_APPNAME value -const char *get_appname(void) +const char *get_appname(bool namelike) { const char *env_val = os_getenv("NVIM_APPNAME"); if (env_val == NULL || *env_val == NUL) { env_val = "nvim"; } + + if (namelike) { + // Appname may be a relative path, replace slashes to make it name-like. + xstrlcpy(NameBuff, env_val, sizeof(NameBuff)); + memchrsub(NameBuff, '/', '-', sizeof(NameBuff)); + memchrsub(NameBuff, '\\', '-', sizeof(NameBuff)); + } + return env_val; } /// Ensure that APPNAME is valid. Must be a name or relative path. bool appname_is_valid(void) { - const char *appname = get_appname(); + const char *appname = get_appname(false); if (path_is_absolute(appname) // TODO(justinmk): on Windows, path_is_absolute says "/" is NOT absolute. Should it? || strequal(appname, "/") @@ -193,7 +203,7 @@ char *get_xdg_home(const XDGVarType idx) FUNC_ATTR_WARN_UNUSED_RESULT { char *dir = stdpaths_get_xdg_var(idx); - const char *appname = get_appname(); + const char *appname = get_appname(false); size_t appname_len = strlen(appname); assert(appname_len < (IOSIZE - sizeof("-data"))); -- cgit From 5d7853f22903a4f42d52f565f6a662c3ef178a8c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 10 Sep 2024 01:14:18 -0700 Subject: refactor(os/input.c): rename os_inchar => input_get #30327 Problem: The name `os_inchar` (from Vim's old `mch_inchar`) is ambiguous: "inchar" sounds like it could be reading or enqueuing (setting) input. Its docstring is also ambiguous. Solution: - Rename `os_inchar` to `input_get`. - Write some mf'ing docstrings. - Add assert() in TRY_READ(). --- src/nvim/os/input.c | 108 +++++++++++++++++++++++++++++----------------------- 1 file changed, 60 insertions(+), 48 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 8affc58591..a240c0e5b6 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -33,12 +33,6 @@ #define READ_BUFFER_SIZE 0xfff #define INPUT_BUFFER_SIZE ((READ_BUFFER_SIZE * 4) + MAX_KEY_CODE_LEN) -typedef enum { - kInputNone, - kInputAvail, - kInputEof, -} InbufPollResult; - static RStream read_stream = { .s.closed = true }; // Input before UI starts. static char input_buffer[INPUT_BUFFER_SIZE]; static char *input_read_pos = input_buffer; @@ -84,57 +78,76 @@ static void cursorhold_event(void **argv) static void create_cursorhold_event(bool events_enabled) { // If events are enabled and the queue has any items, this function should not - // have been called (inbuf_poll would return kInputAvail). + // have been called (`inbuf_poll` would return `kTrue`). // TODO(tarruda): Cursorhold should be implemented as a timer set during the // `state_check` callback for the states where it can be triggered. assert(!events_enabled || multiqueue_empty(main_loop.events)); multiqueue_put(main_loop.events, cursorhold_event, NULL); } -static void restart_cursorhold_wait(int tb_change_cnt) +static void reset_cursorhold_wait(int tb_change_cnt) { cursorhold_time = 0; cursorhold_tb_change_cnt = tb_change_cnt; } -/// Low level input function +/// Reads OS input into `buf`, and consumes pending events while waiting (if `ms != 0`). +/// +/// - Consumes available input received from the OS. +/// - Consumes pending events. +/// - Manages CursorHold events. +/// - Handles EOF conditions. /// -/// Wait until either the input buffer is non-empty or, if `events` is not NULL -/// until `events` is non-empty. -int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *events) +/// Originally based on the Vim `mch_inchar` function. +/// +/// @param buf Buffer to store read input. +/// @param maxlen Maximum bytes to read into `buf`, or 0 to skip reading. +/// @param ms Timeout in milliseconds. -1 for indefinite wait, 0 for no wait. +/// @param tb_change_cnt Used to detect when typeahead changes. +/// @param events (optional) Events to process. +/// @return Bytes read into buf, or 0 if no input was read +int input_get(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *events) { // This check is needed so that feeding typeahead from RPC can prevent CursorHold. if (tb_change_cnt != cursorhold_tb_change_cnt) { - restart_cursorhold_wait(tb_change_cnt); + reset_cursorhold_wait(tb_change_cnt); } - if (maxlen && input_available()) { - restart_cursorhold_wait(tb_change_cnt); - size_t to_read = MIN((size_t)maxlen, input_available()); - memcpy(buf, input_read_pos, to_read); - input_read_pos += to_read; - return (int)to_read; - } +#define TRY_READ() \ + do { \ + if (maxlen && input_available()) { \ + reset_cursorhold_wait(tb_change_cnt); \ + assert(maxlen >= 0); \ + size_t to_read = MIN((size_t)maxlen, input_available()); \ + memcpy(buf, input_read_pos, to_read); \ + input_read_pos += to_read; \ + /* This is safe because INPUT_BUFFER_SIZE fits in an int. */ \ + assert(to_read <= INT_MAX); \ + return (int)to_read; \ + } \ + } while (0) + + TRY_READ(); // No risk of a UI flood, so disable CTRL-C "interrupt" behavior if it's mapped. if ((mapped_ctrl_c | curbuf->b_mapped_ctrl_c) & get_real_state()) { ctrl_c_interrupts = false; } - InbufPollResult result; + TriState result; ///< inbuf_poll result. if (ms >= 0) { - if ((result = inbuf_poll(ms, events)) == kInputNone) { + if ((result = inbuf_poll(ms, events)) == kFalse) { return 0; } } else { uint64_t wait_start = os_hrtime(); cursorhold_time = MIN(cursorhold_time, (int)p_ut); - if ((result = inbuf_poll((int)p_ut - cursorhold_time, events)) == kInputNone) { + if ((result = inbuf_poll((int)p_ut - cursorhold_time, events)) == kFalse) { if (read_stream.s.closed && silent_mode) { // Drained eventloop & initial input; exit silent/batch-mode (-es/-Es). read_error_exit(); } - restart_cursorhold_wait(tb_change_cnt); + reset_cursorhold_wait(tb_change_cnt); if (trigger_cursorhold() && !typebuf_changed(tb_change_cnt)) { create_cursorhold_event(events == main_loop.events); } else { @@ -153,32 +166,26 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e return 0; } - if (maxlen && input_available()) { - restart_cursorhold_wait(tb_change_cnt); - // Safe to convert `to_read` to int, it will never overflow since - // INPUT_BUFFER_SIZE fits in an int - size_t to_read = MIN((size_t)maxlen, input_available()); - memcpy(buf, input_read_pos, to_read); - input_read_pos += to_read; - return (int)to_read; - } + TRY_READ(); // If there are events, return the keys directly if (maxlen && pending_events(events)) { return push_event_key(buf, maxlen); } - if (result == kInputEof) { + if (result == kNone) { read_error_exit(); } return 0; + +#undef TRY_READ } // Check if a character is available for reading bool os_char_avail(void) { - return inbuf_poll(0, NULL) == kInputAvail; + return inbuf_poll(0, NULL) == kTrue; } /// Poll for fast events. `got_int` will be set to `true` if CTRL-C was typed. @@ -463,15 +470,22 @@ bool input_blocking(void) return blocking; } -// This is a replacement for the old `WaitForChar` function in os_unix.c -static InbufPollResult inbuf_poll(int ms, MultiQueue *events) +/// Checks for (but does not read) available input, and consumes `main_loop.events` while waiting. +/// +/// @param ms Timeout in milliseconds. -1 for indefinite wait, 0 for no wait. +/// @param events (optional) Queue to check for pending events. +/// @return TriState: +/// - kTrue: Input/events available +/// - kFalse: No input/events +/// - kNone: EOF reached on the input stream +static TriState inbuf_poll(int ms, MultiQueue *events) { if (os_input_ready(events)) { - return kInputAvail; + return kTrue; } if (do_profiling == PROF_YES && ms) { - prof_inchar_enter(); + prof_input_start(); } if ((ms == -1 || ms > 0) && events != main_loop.events && !input_eof) { @@ -479,20 +493,18 @@ static InbufPollResult inbuf_poll(int ms, MultiQueue *events) blocking = true; multiqueue_process_events(ch_before_blocking_events); } - DLOG("blocking... events_enabled=%d events_pending=%d", events != NULL, - events && !multiqueue_empty(events)); - LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms, - os_input_ready(events) || input_eof); + DLOG("blocking... events=%d pending=%d", !!events, pending_events(events)); + LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms, os_input_ready(events) || input_eof); blocking = false; if (do_profiling == PROF_YES && ms) { - prof_inchar_exit(); + prof_input_end(); } if (os_input_ready(events)) { - return kInputAvail; + return kTrue; } - return input_eof ? kInputEof : kInputNone; + return input_eof ? kNone : kFalse; } static size_t input_read_cb(RStream *stream, const char *buf, size_t c, void *data, bool at_eof) @@ -533,8 +545,8 @@ static void process_ctrl_c(void) } } -// Helper function used to push bytes from the 'event' key sequence partially -// between calls to os_inchar when maxlen < 3 +/// Pushes bytes from the "event" key sequence (KE_EVENT) partially between calls to input_get when +/// `maxlen < 3`. static int push_event_key(uint8_t *buf, int maxlen) { static const uint8_t key[3] = { K_SPECIAL, KS_EXTRA, KE_EVENT }; -- cgit From 5931f780e0282ad486fa070bb05b3877cc1d44f0 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 11 Sep 2024 17:25:00 -0700 Subject: feat(log): use "ui" as default name for TUI client #30345 The default "session name" for the builtin TUI is "ui". before: INF 2024-09-10T14:57:35.385 hello.sock os_exit:692: Nvim exit: 1 INF 2024-09-10T14:57:35.388 ?.4543 os_exit:692: Nvim exit: 1 after: INF 2024-09-10T14:59:19.919 hello.sock os_exit:692: Nvim exit: 1 INF 2024-09-10T14:59:19.922 ui.5684 os_exit:692: Nvim exit: 1 --- src/nvim/os/input.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index a240c0e5b6..7c5293a8b8 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -100,7 +100,7 @@ static void reset_cursorhold_wait(int tb_change_cnt) /// /// Originally based on the Vim `mch_inchar` function. /// -/// @param buf Buffer to store read input. +/// @param buf Buffer to store consumed input. /// @param maxlen Maximum bytes to read into `buf`, or 0 to skip reading. /// @param ms Timeout in milliseconds. -1 for indefinite wait, 0 for no wait. /// @param tb_change_cnt Used to detect when typeahead changes. @@ -493,7 +493,7 @@ static TriState inbuf_poll(int ms, MultiQueue *events) blocking = true; multiqueue_process_events(ch_before_blocking_events); } - DLOG("blocking... events=%d pending=%d", !!events, pending_events(events)); + DLOG("blocking... events=%s", !!events ? "true" : "false"); LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms, os_input_ready(events) || input_eof); blocking = false; -- cgit From deac7df80a1491ae65b68a1a1047902bcd775adc Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 12 Sep 2024 09:16:57 -0700 Subject: refactor(stream.c): unused params in stream_close #30356 --- src/nvim/os/shell.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 4b34ed1c4e..0b082c164d 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -1292,7 +1292,7 @@ static void shell_write_cb(Stream *stream, void *data, int status) msg_schedule_semsg(_("E5677: Error writing input to shell-command: %s"), uv_err_name(status)); } - stream_close(stream, NULL, NULL, false); + stream_may_close(stream, false); } /// Applies 'shellxescape' (p_sxe) and 'shellxquote' (p_sxq) to a command. -- cgit From 057d27a9d6ef0bb2ee5130704c45b9e9197e7c36 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 15 Sep 2024 12:20:58 -0700 Subject: refactor: rename "process" => "proc" #30387 Problem: - "process" is often used as a verb (`multiqueue_process_events`), which is ambiguous for cases where it's used as a topic. - The documented naming convention for processes is "proc". - `:help dev-name-common` - Shorter is better, when it doesn't harm readability or discoverability. Solution: Rename "process" => "proc" in all C symbols and module names. --- src/nvim/os/env.c | 2 +- src/nvim/os/proc.c | 286 +++++++++++++++++++++++++++ src/nvim/os/proc.h | 11 ++ src/nvim/os/process.c | 286 --------------------------- src/nvim/os/process.h | 11 -- src/nvim/os/pty_conpty_win.c | 4 +- src/nvim/os/pty_proc.h | 7 + src/nvim/os/pty_proc_unix.c | 417 ++++++++++++++++++++++++++++++++++++++ src/nvim/os/pty_proc_unix.h | 18 ++ src/nvim/os/pty_proc_win.c | 440 +++++++++++++++++++++++++++++++++++++++++ src/nvim/os/pty_proc_win.h | 27 +++ src/nvim/os/pty_process.h | 7 - src/nvim/os/pty_process_unix.c | 417 -------------------------------------- src/nvim/os/pty_process_unix.h | 18 -- src/nvim/os/pty_process_win.c | 440 ----------------------------------------- src/nvim/os/pty_process_win.h | 27 --- src/nvim/os/shell.c | 14 +- 17 files changed, 1216 insertions(+), 1216 deletions(-) create mode 100644 src/nvim/os/proc.c create mode 100644 src/nvim/os/proc.h delete mode 100644 src/nvim/os/process.c delete mode 100644 src/nvim/os/process.h create mode 100644 src/nvim/os/pty_proc.h create mode 100644 src/nvim/os/pty_proc_unix.c create mode 100644 src/nvim/os/pty_proc_unix.h create mode 100644 src/nvim/os/pty_proc_win.c create mode 100644 src/nvim/os/pty_proc_win.h delete mode 100644 src/nvim/os/pty_process.h delete mode 100644 src/nvim/os/pty_process_unix.c delete mode 100644 src/nvim/os/pty_process_unix.h delete mode 100644 src/nvim/os/pty_process_win.c delete mode 100644 src/nvim/os/pty_process_win.h (limited to 'src/nvim/os') diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 8dfedd073e..ccf6c9554a 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -344,7 +344,7 @@ char *os_getenvname_at_index(size_t index) #endif } -/// Get the process ID of the Neovim process. +/// Get the process ID of the Nvim process. /// /// @return the process ID. int64_t os_get_pid(void) diff --git a/src/nvim/os/proc.c b/src/nvim/os/proc.c new file mode 100644 index 0000000000..1670e469ee --- /dev/null +++ b/src/nvim/os/proc.c @@ -0,0 +1,286 @@ +/// OS process functions +/// +/// psutil is a good reference for cross-platform syscall voodoo: +/// https://github.com/giampaolo/psutil/tree/master/psutil/arch + +// IWYU pragma: no_include + +#include +#include +#include +#include +#include + +#ifdef MSWIN +# include +#endif + +#if defined(__FreeBSD__) +# include +# include +# include +#endif + +#if defined(__NetBSD__) || defined(__OpenBSD__) +# include +#endif + +#if defined(__APPLE__) || defined(BSD) +# include + +# include "nvim/macros_defs.h" +#endif + +#if defined(__linux__) +# include +#endif + +#include "nvim/log.h" +#include "nvim/memory.h" +#include "nvim/os/proc.h" + +#ifdef MSWIN +# include "nvim/api/private/helpers.h" +#endif + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/proc.c.generated.h" +#endif + +#ifdef MSWIN +static bool os_proc_tree_kill_rec(HANDLE proc, int sig) +{ + if (proc == NULL) { + return false; + } + PROCESSENTRY32 pe; + DWORD pid = GetProcessId(proc); + + 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(proc, (unsigned)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); + if (pid == 0) { + // Never kill self (pid=0). + return false; + } + ILOG("sending %s to PID %d", sig == SIGTERM ? "SIGTERM" : "SIGKILL", -pid); + return uv_kill(-pid, sig) == 0; +} +#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) + FUNC_ATTR_NONNULL_ALL +{ + if (ppid < 0) { + return 2; + } + + int *temp = NULL; + *proc_list = NULL; + *proc_count = 0; + +#ifdef MSWIN + 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 +# ifdef __NetBSD__ + static int name[] = { + CTL_KERN, KERN_PROC2, KERN_PROC_ALL, 0, (int)(sizeof(struct kinfo_proc2)), 0 + }; +# else + static int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; +# endif + + // 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. +# ifdef __NetBSD__ + struct kinfo_proc2 *p_list = xmalloc(len); +# else + struct kinfo_proc *p_list = xmalloc(len); +# endif + 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 MSWIN +/// 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, Arena *arena) +{ + 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) { + pinfo = arena_dict(arena, 3); + PUT_C(pinfo, "pid", INTEGER_OBJ(pid)); + PUT_C(pinfo, "ppid", INTEGER_OBJ((int)pe.th32ParentProcessID)); + PUT_C(pinfo, "name", CSTR_TO_ARENA_OBJ(arena, pe.szExeFile)); + } + + return pinfo; +} +#endif + +/// Return true if process `pid` is running. +bool os_proc_running(int pid) +{ + int err = uv_kill(pid, 0); + // If there is no error the process must be running. + if (err == 0) { + return true; + } + // If the error is ESRCH then the process is not running. + if (err == UV_ESRCH) { + return false; + } + // If the process is running and owned by another user we get EPERM. With + // other errors the process might be running, assuming it is then. + return true; +} diff --git a/src/nvim/os/proc.h b/src/nvim/os/proc.h new file mode 100644 index 0000000000..1831f21cc3 --- /dev/null +++ b/src/nvim/os/proc.h @@ -0,0 +1,11 @@ +#pragma once + +#include // IWYU pragma: keep + +#ifdef MSWIN +# include "nvim/api/private/defs.h" // IWYU pragma: keep +#endif + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/proc.h.generated.h" +#endif diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c deleted file mode 100644 index e8d38d5b8a..0000000000 --- a/src/nvim/os/process.c +++ /dev/null @@ -1,286 +0,0 @@ -/// OS process functions -/// -/// psutil is a good reference for cross-platform syscall voodoo: -/// https://github.com/giampaolo/psutil/tree/master/psutil/arch - -// IWYU pragma: no_include - -#include -#include -#include -#include -#include - -#ifdef MSWIN -# include -#endif - -#if defined(__FreeBSD__) -# include -# include -# include -#endif - -#if defined(__NetBSD__) || defined(__OpenBSD__) -# include -#endif - -#if defined(__APPLE__) || defined(BSD) -# include - -# include "nvim/macros_defs.h" -#endif - -#if defined(__linux__) -# include -#endif - -#include "nvim/log.h" -#include "nvim/memory.h" -#include "nvim/os/process.h" - -#ifdef MSWIN -# include "nvim/api/private/helpers.h" -#endif - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os/process.c.generated.h" -#endif - -#ifdef MSWIN -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)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); - if (pid == 0) { - // Never kill self (pid=0). - return false; - } - ILOG("sending %s to PID %d", sig == SIGTERM ? "SIGTERM" : "SIGKILL", -pid); - return uv_kill(-pid, sig) == 0; -} -#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) - FUNC_ATTR_NONNULL_ALL -{ - if (ppid < 0) { - return 2; - } - - int *temp = NULL; - *proc_list = NULL; - *proc_count = 0; - -#ifdef MSWIN - 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 -# ifdef __NetBSD__ - static int name[] = { - CTL_KERN, KERN_PROC2, KERN_PROC_ALL, 0, (int)(sizeof(struct kinfo_proc2)), 0 - }; -# else - static int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; -# endif - - // 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. -# ifdef __NetBSD__ - struct kinfo_proc2 *p_list = xmalloc(len); -# else - struct kinfo_proc *p_list = xmalloc(len); -# endif - 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 MSWIN -/// 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, Arena *arena) -{ - 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) { - pinfo = arena_dict(arena, 3); - PUT_C(pinfo, "pid", INTEGER_OBJ(pid)); - PUT_C(pinfo, "ppid", INTEGER_OBJ((int)pe.th32ParentProcessID)); - PUT_C(pinfo, "name", CSTR_TO_ARENA_OBJ(arena, pe.szExeFile)); - } - - return pinfo; -} -#endif - -/// Return true if process `pid` is running. -bool os_proc_running(int pid) -{ - int err = uv_kill(pid, 0); - // If there is no error the process must be running. - if (err == 0) { - return true; - } - // If the error is ESRCH then the process is not running. - if (err == UV_ESRCH) { - return false; - } - // If the process is running and owned by another user we get EPERM. With - // other errors the process might be running, assuming it is then. - return true; -} diff --git a/src/nvim/os/process.h b/src/nvim/os/process.h deleted file mode 100644 index 3b116b4bad..0000000000 --- a/src/nvim/os/process.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include // IWYU pragma: keep - -#ifdef MSWIN -# include "nvim/api/private/defs.h" // IWYU pragma: keep -#endif - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os/process.h.generated.h" -#endif diff --git a/src/nvim/os/pty_conpty_win.c b/src/nvim/os/pty_conpty_win.c index e7697880af..6402330a52 100644 --- a/src/nvim/os/pty_conpty_win.c +++ b/src/nvim/os/pty_conpty_win.c @@ -143,7 +143,7 @@ finished: return conpty_object; } -bool os_conpty_spawn(conpty_t *conpty_object, HANDLE *process_handle, wchar_t *name, +bool os_conpty_spawn(conpty_t *conpty_object, HANDLE *proc_handle, wchar_t *name, wchar_t *cmd_line, wchar_t *cwd, wchar_t *env) { PROCESS_INFORMATION pi = { 0 }; @@ -159,7 +159,7 @@ bool os_conpty_spawn(conpty_t *conpty_object, HANDLE *process_handle, wchar_t *n &pi)) { return false; } - *process_handle = pi.hProcess; + *proc_handle = pi.hProcess; return true; } diff --git a/src/nvim/os/pty_proc.h b/src/nvim/os/pty_proc.h new file mode 100644 index 0000000000..d815aae69c --- /dev/null +++ b/src/nvim/os/pty_proc.h @@ -0,0 +1,7 @@ +#pragma once + +#ifdef MSWIN +# include "nvim/os/pty_proc_win.h" +#else +# include "nvim/os/pty_proc_unix.h" +#endif diff --git a/src/nvim/os/pty_proc_unix.c b/src/nvim/os/pty_proc_unix.c new file mode 100644 index 0000000000..9e9303ed48 --- /dev/null +++ b/src/nvim/os/pty_proc_unix.c @@ -0,0 +1,417 @@ +// Some of the code came from pangoterm and libuv + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// forkpty is not in POSIX, so headers are platform-specific +#if defined(__FreeBSD__) || defined(__DragonFly__) +# include +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) +# include +#elif defined(__sun) +# include +# include +# include +# include +# include +#else +# include +#endif + +#ifdef __APPLE__ +# include +#endif + +#include "auto/config.h" +#include "klib/klist.h" +#include "nvim/eval/typval.h" +#include "nvim/event/defs.h" +#include "nvim/event/loop.h" +#include "nvim/event/proc.h" +#include "nvim/log.h" +#include "nvim/os/fs.h" +#include "nvim/os/os_defs.h" +#include "nvim/os/pty_proc.h" +#include "nvim/os/pty_proc_unix.h" +#include "nvim/types_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/pty_proc_unix.c.generated.h" +#endif + +#if defined(__sun) && !defined(HAVE_FORKPTY) + +// this header defines STR, just as nvim.h, but it is defined as ('S'<<8), +// to avoid #undef STR, #undef STR, #define STR ('S'<<8) just delay the +// inclusion of the header even though it gets include out of order. +# include + +static int openpty(int *amaster, int *aslave, char *name, struct termios *termp, + struct winsize *winp) +{ + int slave = -1; + int master = open("/dev/ptmx", O_RDWR); + if (master == -1) { + goto error; + } + + // grantpt will invoke a setuid program to change permissions + // and might fail if SIGCHLD handler is set, temporarily reset + // while running + void (*sig_saved)(int) = signal(SIGCHLD, SIG_DFL); + int res = grantpt(master); + signal(SIGCHLD, sig_saved); + + if (res == -1 || unlockpt(master) == -1) { + goto error; + } + + char *slave_name = ptsname(master); + if (slave_name == NULL) { + goto error; + } + + slave = open(slave_name, O_RDWR|O_NOCTTY); + if (slave == -1) { + goto error; + } + + // ptem emulates a terminal when used on a pseudo terminal driver, + // must be pushed before ldterm + ioctl(slave, I_PUSH, "ptem"); + // ldterm provides most of the termio terminal interface + ioctl(slave, I_PUSH, "ldterm"); + // ttcompat compatibility with older terminal ioctls + ioctl(slave, I_PUSH, "ttcompat"); + + if (termp) { + tcsetattr(slave, TCSAFLUSH, termp); + } + if (winp) { + ioctl(slave, TIOCSWINSZ, winp); + } + + *amaster = master; + *aslave = slave; + // ignoring name, not passed and size is unknown in the API + + return 0; + +error: + if (slave != -1) { + close(slave); + } + if (master != -1) { + close(master); + } + return -1; +} + +static int login_tty(int fd) +{ + setsid(); + if (ioctl(fd, TIOCSCTTY, NULL) == -1) { + return -1; + } + + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + if (fd > STDERR_FILENO) { + close(fd); + } + + return 0; +} + +static pid_t forkpty(int *amaster, char *name, struct termios *termp, struct winsize *winp) +{ + int master, slave; + if (openpty(&master, &slave, name, termp, winp) == -1) { + return -1; + } + + pid_t pid = fork(); + switch (pid) { + case -1: + close(master); + close(slave); + return -1; + case 0: + close(master); + login_tty(slave); + return 0; + default: + close(slave); + *amaster = master; + return pid; + } +} + +#endif + +/// @returns zero on success, or negative error code +int pty_proc_spawn(PtyProc *ptyproc) + FUNC_ATTR_NONNULL_ALL +{ + // termios initialized at first use + static struct termios termios_default; + if (!termios_default.c_cflag) { + init_termios(&termios_default); + } + + int status = 0; // zero or negative error code (libuv convention) + Proc *proc = (Proc *)ptyproc; + assert(proc->err.s.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_default, &ptyproc->winsize); + + if (pid < 0) { + status = -errno; + ELOG("forkpty failed: %s", strerror(errno)); + return status; + } else if (pid == 0) { + init_child(ptyproc); // never returns + } + + // make sure the master file descriptor is non blocking + int master_status_flags = fcntl(master, F_GETFL); + if (master_status_flags == -1) { + status = -errno; + ELOG("Failed to get master descriptor status flags: %s", strerror(errno)); + goto error; + } + if (fcntl(master, F_SETFL, master_status_flags | O_NONBLOCK) == -1) { + status = -errno; + ELOG("Failed to make master descriptor non-blocking: %s", strerror(errno)); + goto error; + } + + // Other jobs and providers should not get a copy of this file descriptor. + if (os_set_cloexec(master) == -1) { + status = -errno; + ELOG("Failed to set CLOEXEC on ptmx file descriptor"); + goto error; + } + + if (!proc->in.closed + && (status = set_duplicating_descriptor(master, &proc->in.uv.pipe))) { + goto error; + } + if (!proc->out.s.closed + && (status = set_duplicating_descriptor(master, &proc->out.s.uv.pipe))) { + goto error; + } + + ptyproc->tty_fd = master; + proc->pid = pid; + return 0; + +error: + close(master); + kill(pid, SIGKILL); + waitpid(pid, NULL, 0); + return status; +} + +const char *pty_proc_tty_name(PtyProc *ptyproc) +{ + return ptsname(ptyproc->tty_fd); +} + +void pty_proc_resize(PtyProc *ptyproc, uint16_t width, uint16_t height) + FUNC_ATTR_NONNULL_ALL +{ + ptyproc->winsize = (struct winsize){ height, width, 0, 0 }; + ioctl(ptyproc->tty_fd, TIOCSWINSZ, &ptyproc->winsize); +} + +void pty_proc_close(PtyProc *ptyproc) + FUNC_ATTR_NONNULL_ALL +{ + pty_proc_close_master(ptyproc); + Proc *proc = (Proc *)ptyproc; + if (proc->internal_close_cb) { + proc->internal_close_cb(proc); + } +} + +void pty_proc_close_master(PtyProc *ptyproc) FUNC_ATTR_NONNULL_ALL +{ + if (ptyproc->tty_fd >= 0) { + close(ptyproc->tty_fd); + ptyproc->tty_fd = -1; + } +} + +void pty_proc_teardown(Loop *loop) +{ + uv_signal_stop(&loop->children_watcher); +} + +static void init_child(PtyProc *ptyproc) + FUNC_ATTR_NONNULL_ALL +{ +#if defined(HAVE__NSGETENVIRON) +# define environ (*_NSGetEnviron()) +#else + extern char **environ; +#endif + // New session/process-group. #6530 + setsid(); + + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + Proc *proc = (Proc *)ptyproc; + if (proc->cwd && os_chdir(proc->cwd) != 0) { + ELOG("chdir(%s) failed: %s", proc->cwd, strerror(errno)); + return; + } + + const char *prog = proc_get_exepath(proc); + + assert(proc->env); + environ = tv_dict_to_env(proc->env); + execvp(prog, proc->argv); + ELOG("execvp(%s) failed: %s", prog, strerror(errno)); + + _exit(122); // 122 is EXEC_FAILED in the Vim source. +} + +static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL +{ + // Taken from pangoterm + termios->c_iflag = ICRNL|IXON; + termios->c_oflag = OPOST|ONLCR; +#ifdef TAB0 + termios->c_oflag |= TAB0; +#endif + termios->c_cflag = CS8|CREAD; + termios->c_lflag = ISIG|ICANON|IEXTEN|ECHO|ECHOE|ECHOK; + + // not using cfsetspeed, not available on all platforms + cfsetispeed(termios, 38400); + cfsetospeed(termios, 38400); + +#ifdef IUTF8 + termios->c_iflag |= IUTF8; +#endif +#ifdef NL0 + termios->c_oflag |= NL0; +#endif +#ifdef CR0 + termios->c_oflag |= CR0; +#endif +#ifdef BS0 + termios->c_oflag |= BS0; +#endif +#ifdef VT0 + termios->c_oflag |= VT0; +#endif +#ifdef FF0 + termios->c_oflag |= FF0; +#endif +#ifdef ECHOCTL + termios->c_lflag |= ECHOCTL; +#endif +#ifdef ECHOKE + termios->c_lflag |= ECHOKE; +#endif + + termios->c_cc[VINTR] = 0x1f & 'C'; + termios->c_cc[VQUIT] = 0x1f & '\\'; + termios->c_cc[VERASE] = 0x7f; + termios->c_cc[VKILL] = 0x1f & 'U'; + termios->c_cc[VEOF] = 0x1f & 'D'; + termios->c_cc[VEOL] = _POSIX_VDISABLE; + termios->c_cc[VEOL2] = _POSIX_VDISABLE; + termios->c_cc[VSTART] = 0x1f & 'Q'; + termios->c_cc[VSTOP] = 0x1f & 'S'; + termios->c_cc[VSUSP] = 0x1f & 'Z'; + termios->c_cc[VREPRINT] = 0x1f & 'R'; + termios->c_cc[VWERASE] = 0x1f & 'W'; + termios->c_cc[VLNEXT] = 0x1f & 'V'; + termios->c_cc[VMIN] = 1; + termios->c_cc[VTIME] = 0; +} + +static int set_duplicating_descriptor(int fd, uv_pipe_t *pipe) + FUNC_ATTR_NONNULL_ALL +{ + int status = 0; // zero or negative error code (libuv convention) + int fd_dup = dup(fd); + if (fd_dup < 0) { + status = -errno; + ELOG("Failed to dup descriptor %d: %s", fd, strerror(errno)); + return status; + } + + if (os_set_cloexec(fd_dup) == -1) { + status = -errno; + ELOG("Failed to set CLOEXEC on duplicate fd"); + goto error; + } + + status = uv_pipe_open(pipe, fd_dup); + if (status) { + ELOG("Failed to set pipe to descriptor %d: %s", + fd_dup, uv_strerror(status)); + goto error; + } + return status; + +error: + close(fd_dup); + return status; +} + +static void chld_handler(uv_signal_t *handle, int signum) +{ + int stat = 0; + int pid; + + Loop *loop = handle->loop->data; + + kl_iter(WatcherPtr, loop->children, current) { + Proc *proc = (*current)->data; + do { + pid = waitpid(proc->pid, &stat, WNOHANG); + } while (pid < 0 && errno == EINTR); + + if (pid <= 0) { + continue; + } + + if (WIFEXITED(stat)) { + proc->status = WEXITSTATUS(stat); + } else if (WIFSIGNALED(stat)) { + proc->status = 128 + WTERMSIG(stat); + } + proc->internal_exit_cb(proc); + } +} + +PtyProc pty_proc_init(Loop *loop, void *data) +{ + PtyProc rv; + rv.proc = proc_init(loop, kProcTypePty, data); + rv.width = 80; + rv.height = 24; + rv.tty_fd = -1; + return rv; +} diff --git a/src/nvim/os/pty_proc_unix.h b/src/nvim/os/pty_proc_unix.h new file mode 100644 index 0000000000..47f9af088e --- /dev/null +++ b/src/nvim/os/pty_proc_unix.h @@ -0,0 +1,18 @@ +#pragma once +// IWYU pragma: private, include "nvim/os/pty_proc.h" + +#include +#include + +#include "nvim/event/defs.h" + +typedef struct { + Proc proc; + uint16_t width, height; + struct winsize winsize; + int tty_fd; +} PtyProc; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/pty_proc_unix.h.generated.h" +#endif diff --git a/src/nvim/os/pty_proc_win.c b/src/nvim/os/pty_proc_win.c new file mode 100644 index 0000000000..5bd6eead51 --- /dev/null +++ b/src/nvim/os/pty_proc_win.c @@ -0,0 +1,440 @@ +#include +#include +#include + +#include "nvim/ascii_defs.h" +#include "nvim/eval/typval.h" +#include "nvim/event/loop.h" +#include "nvim/log.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" +#include "nvim/os/os.h" +#include "nvim/os/pty_conpty_win.h" +#include "nvim/os/pty_proc_win.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/pty_proc_win.c.generated.h" +#endif + +static void CALLBACK pty_proc_finish1(void *context, BOOLEAN unused) + FUNC_ATTR_NONNULL_ALL +{ + PtyProc *ptyproc = (PtyProc *)context; + Proc *proc = (Proc *)ptyproc; + + os_conpty_free(ptyproc->conpty); + // NB: pty_proc_finish1() is called on a separate thread, + // but the timer only works properly if it's started by the main thread. + loop_schedule_fast(proc->loop, event_create(start_wait_eof_timer, ptyproc)); +} + +static void start_wait_eof_timer(void **argv) + FUNC_ATTR_NONNULL_ALL +{ + PtyProc *ptyproc = (PtyProc *)argv[0]; + + if (ptyproc->finish_wait != NULL) { + uv_timer_start(&ptyproc->wait_eof_timer, wait_eof_timer_cb, 200, 200); + } +} + +/// @returns zero on success, or negative error code. +int pty_proc_spawn(PtyProc *ptyproc) + FUNC_ATTR_NONNULL_ALL +{ + Proc *proc = (Proc *)ptyproc; + int status = 0; + conpty_t *conpty_object = NULL; + char *in_name = NULL; + char *out_name = NULL; + HANDLE proc_handle = NULL; + uv_connect_t *in_req = NULL; + uv_connect_t *out_req = NULL; + wchar_t *cmd_line = NULL; + wchar_t *cwd = NULL; + wchar_t *env = NULL; + const char *emsg = NULL; + + assert(proc->err.s.closed); + + if (!os_has_conpty_working() || (conpty_object = os_conpty_init(&in_name, + &out_name, ptyproc->width, + ptyproc->height)) == NULL) { + status = UV_ENOSYS; + 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_proc_connect_cb); + } + + if (!proc->out.s.closed) { + out_req = xmalloc(sizeof(uv_connect_t)); + uv_pipe_connect(out_req, + &proc->out.s.uv.pipe, + out_name, + pty_proc_connect_cb); + } + + if (proc->cwd != NULL) { + status = utf8_to_utf16(proc->cwd, -1, &cwd); + if (status != 0) { + emsg = "utf8_to_utf16(proc->cwd) failed"; + goto cleanup; + } + } + + status = build_cmd_line(proc->argv, &cmd_line, + os_shell_is_cmdexe(proc->argv[0])); + if (status != 0) { + emsg = "build_cmd_line failed"; + goto cleanup; + } + + if (proc->env != NULL) { + status = build_env_block(proc->env, &env); + } + + if (status != 0) { + emsg = "build_env_block failed"; + goto cleanup; + } + + if (!os_conpty_spawn(conpty_object, + &proc_handle, + NULL, + cmd_line, + cwd, + env)) { + emsg = "os_conpty_spawn failed"; + status = (int)GetLastError(); + goto cleanup; + } + proc->pid = (int)GetProcessId(proc_handle); + + uv_timer_init(&proc->loop->uv, &ptyproc->wait_eof_timer); + ptyproc->wait_eof_timer.data = (void *)ptyproc; + if (!RegisterWaitForSingleObject(&ptyproc->finish_wait, + proc_handle, + pty_proc_finish1, + ptyproc, + INFINITE, + WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE)) { + abort(); + } + + // Wait until pty_proc_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->conpty = conpty_object; + ptyproc->proc_handle = proc_handle; + conpty_object = NULL; + proc_handle = NULL; + +cleanup: + if (status) { + // In the case of an error of MultiByteToWideChar or CreateProcessW. + ELOG("pty_proc_spawn(%s): %s: error code: %d", + proc->argv[0], emsg, status); + status = os_translate_sys_error(status); + } + os_conpty_free(conpty_object); + xfree(in_name); + xfree(out_name); + if (proc_handle != NULL) { + CloseHandle(proc_handle); + } + xfree(in_req); + xfree(out_req); + xfree(cmd_line); + xfree(env); + xfree(cwd); + return status; +} + +const char *pty_proc_tty_name(PtyProc *ptyproc) +{ + return "?"; +} + +void pty_proc_resize(PtyProc *ptyproc, uint16_t width, uint16_t height) + FUNC_ATTR_NONNULL_ALL +{ + os_conpty_set_size(ptyproc->conpty, width, height); +} + +void pty_proc_close(PtyProc *ptyproc) + FUNC_ATTR_NONNULL_ALL +{ + Proc *proc = (Proc *)ptyproc; + + pty_proc_close_master(ptyproc); + + if (ptyproc->finish_wait != NULL) { + UnregisterWaitEx(ptyproc->finish_wait, NULL); + ptyproc->finish_wait = NULL; + uv_close((uv_handle_t *)&ptyproc->wait_eof_timer, NULL); + } + if (ptyproc->proc_handle != NULL) { + CloseHandle(ptyproc->proc_handle); + ptyproc->proc_handle = NULL; + } + + if (proc->internal_close_cb) { + proc->internal_close_cb(proc); + } +} + +void pty_proc_close_master(PtyProc *ptyproc) + FUNC_ATTR_NONNULL_ALL +{ +} + +void pty_proc_teardown(Loop *loop) + FUNC_ATTR_NONNULL_ALL +{ +} + +static void pty_proc_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 +{ + PtyProc *ptyproc = wait_eof_timer->data; + Proc *proc = (Proc *)ptyproc; + + assert(ptyproc->finish_wait != NULL); + if (proc->out.s.closed || proc->out.did_eof || !uv_is_readable(proc->out.s.uvstream)) { + uv_timer_stop(&ptyproc->wait_eof_timer); + pty_proc_finish2(ptyproc); + } +} + +static void pty_proc_finish2(PtyProc *ptyproc) + FUNC_ATTR_NONNULL_ALL +{ + Proc *proc = (Proc *)ptyproc; + + DWORD exit_code = 0; + GetExitCodeProcess(ptyproc->proc_handle, &exit_code); + proc->status = proc->exit_signal ? 128 + proc->exit_signal : (int)exit_code; + + 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 built 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; + QUEUE *q; + QUEUE_FOREACH(q, &args_q, { + ArgNode *arg_node = QUEUE_DATA(q, ArgNode, node); + xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len); + QUEUE_REMOVE(q); + xfree(arg_node->arg); + xfree(arg_node); + if (!QUEUE_EMPTY(&args_q)) { + xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len); + } + }) + + int result = utf8_to_utf16(utf8_cmd_line, -1, 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--; + } +} + +typedef struct EnvNode { + wchar_t *str; + size_t len; + QUEUE node; +} EnvNode; + +/// Build the environment block to pass to CreateProcessW. +/// +/// @param[in] denv Dict of environment name/value pairs +/// @param[out] env Allocated environment block +/// +/// @returns zero on success or error code of MultiByteToWideChar function. +static int build_env_block(dict_T *denv, wchar_t **env_block) +{ + const size_t denv_size = (size_t)tv_dict_len(denv); + size_t env_block_len = 0; + int rc = 0; + char **env = tv_dict_to_env(denv); + + QUEUE *q; + QUEUE env_q; + QUEUE_INIT(&env_q); + // Convert env vars to wchar_t and calculate how big the final env block + // needs to be + for (size_t i = 0; i < denv_size; i++) { + EnvNode *env_node = xmalloc(sizeof(*env_node)); + rc = utf8_to_utf16(env[i], -1, &env_node->str); + if (rc != 0) { + goto cleanup; + } + env_node->len = wcslen(env_node->str) + 1; + env_block_len += env_node->len; + QUEUE_INSERT_TAIL(&env_q, &env_node->node); + } + + // Additional NUL after the final entry + env_block_len++; + + *env_block = xmalloc(sizeof(**env_block) * env_block_len); + wchar_t *pos = *env_block; + + QUEUE_FOREACH(q, &env_q, { + EnvNode *env_node = QUEUE_DATA(q, EnvNode, node); + memcpy(pos, env_node->str, env_node->len * sizeof(*pos)); + pos += env_node->len; + }) + + *pos = L'\0'; + +cleanup: + q = QUEUE_HEAD(&env_q); + while (q != &env_q) { + QUEUE *next = q->next; + EnvNode *env_node = QUEUE_DATA(q, EnvNode, node); + XFREE_CLEAR(env_node->str); + QUEUE_REMOVE(q); + xfree(env_node); + q = next; + } + + return rc; +} + +PtyProc pty_proc_init(Loop *loop, void *data) +{ + PtyProc rv; + rv.proc = proc_init(loop, kProcTypePty, data); + rv.width = 80; + rv.height = 24; + rv.conpty = NULL; + rv.finish_wait = NULL; + rv.proc_handle = NULL; + return rv; +} diff --git a/src/nvim/os/pty_proc_win.h b/src/nvim/os/pty_proc_win.h new file mode 100644 index 0000000000..c2fdea506e --- /dev/null +++ b/src/nvim/os/pty_proc_win.h @@ -0,0 +1,27 @@ +#pragma once +// IWYU pragma: private, include "nvim/os/pty_proc.h" + +#include + +#include "nvim/event/proc.h" +#include "nvim/lib/queue_defs.h" +#include "nvim/os/pty_conpty_win.h" + +typedef struct pty_process { + Proc proc; + uint16_t width, height; + conpty_t *conpty; + HANDLE finish_wait; + HANDLE proc_handle; + uv_timer_t wait_eof_timer; +} PtyProc; + +// Structure used by build_cmd_line() +typedef struct arg_node { + char *arg; // pointer to argument. + QUEUE node; // QUEUE structure. +} ArgNode; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/pty_proc_win.h.generated.h" +#endif diff --git a/src/nvim/os/pty_process.h b/src/nvim/os/pty_process.h deleted file mode 100644 index 2c7a5f66bd..0000000000 --- a/src/nvim/os/pty_process.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#ifdef MSWIN -# include "nvim/os/pty_process_win.h" -#else -# include "nvim/os/pty_process_unix.h" -#endif diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c deleted file mode 100644 index cfa4dcada7..0000000000 --- a/src/nvim/os/pty_process_unix.c +++ /dev/null @@ -1,417 +0,0 @@ -// Some of the code came from pangoterm and libuv - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// forkpty is not in POSIX, so headers are platform-specific -#if defined(__FreeBSD__) || defined(__DragonFly__) -# include -#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) -# include -#elif defined(__sun) -# include -# include -# include -# include -# include -#else -# include -#endif - -#ifdef __APPLE__ -# include -#endif - -#include "auto/config.h" -#include "klib/klist.h" -#include "nvim/eval/typval.h" -#include "nvim/event/defs.h" -#include "nvim/event/loop.h" -#include "nvim/event/process.h" -#include "nvim/log.h" -#include "nvim/os/fs.h" -#include "nvim/os/os_defs.h" -#include "nvim/os/pty_process.h" -#include "nvim/os/pty_process_unix.h" -#include "nvim/types_defs.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os/pty_process_unix.c.generated.h" -#endif - -#if defined(__sun) && !defined(HAVE_FORKPTY) - -// this header defines STR, just as nvim.h, but it is defined as ('S'<<8), -// to avoid #undef STR, #undef STR, #define STR ('S'<<8) just delay the -// inclusion of the header even though it gets include out of order. -# include - -static int openpty(int *amaster, int *aslave, char *name, struct termios *termp, - struct winsize *winp) -{ - int slave = -1; - int master = open("/dev/ptmx", O_RDWR); - if (master == -1) { - goto error; - } - - // grantpt will invoke a setuid program to change permissions - // and might fail if SIGCHLD handler is set, temporarily reset - // while running - void (*sig_saved)(int) = signal(SIGCHLD, SIG_DFL); - int res = grantpt(master); - signal(SIGCHLD, sig_saved); - - if (res == -1 || unlockpt(master) == -1) { - goto error; - } - - char *slave_name = ptsname(master); - if (slave_name == NULL) { - goto error; - } - - slave = open(slave_name, O_RDWR|O_NOCTTY); - if (slave == -1) { - goto error; - } - - // ptem emulates a terminal when used on a pseudo terminal driver, - // must be pushed before ldterm - ioctl(slave, I_PUSH, "ptem"); - // ldterm provides most of the termio terminal interface - ioctl(slave, I_PUSH, "ldterm"); - // ttcompat compatibility with older terminal ioctls - ioctl(slave, I_PUSH, "ttcompat"); - - if (termp) { - tcsetattr(slave, TCSAFLUSH, termp); - } - if (winp) { - ioctl(slave, TIOCSWINSZ, winp); - } - - *amaster = master; - *aslave = slave; - // ignoring name, not passed and size is unknown in the API - - return 0; - -error: - if (slave != -1) { - close(slave); - } - if (master != -1) { - close(master); - } - return -1; -} - -static int login_tty(int fd) -{ - setsid(); - if (ioctl(fd, TIOCSCTTY, NULL) == -1) { - return -1; - } - - dup2(fd, STDIN_FILENO); - dup2(fd, STDOUT_FILENO); - dup2(fd, STDERR_FILENO); - if (fd > STDERR_FILENO) { - close(fd); - } - - return 0; -} - -static pid_t forkpty(int *amaster, char *name, struct termios *termp, struct winsize *winp) -{ - int master, slave; - if (openpty(&master, &slave, name, termp, winp) == -1) { - return -1; - } - - pid_t pid = fork(); - switch (pid) { - case -1: - close(master); - close(slave); - return -1; - case 0: - close(master); - login_tty(slave); - return 0; - default: - close(slave); - *amaster = master; - return pid; - } -} - -#endif - -/// @returns zero on success, or negative error code -int pty_process_spawn(PtyProcess *ptyproc) - FUNC_ATTR_NONNULL_ALL -{ - // termios initialized at first use - static struct termios termios_default; - if (!termios_default.c_cflag) { - init_termios(&termios_default); - } - - int status = 0; // zero or negative error code (libuv convention) - Process *proc = (Process *)ptyproc; - assert(proc->err.s.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_default, &ptyproc->winsize); - - if (pid < 0) { - status = -errno; - ELOG("forkpty failed: %s", strerror(errno)); - return status; - } else if (pid == 0) { - init_child(ptyproc); // never returns - } - - // make sure the master file descriptor is non blocking - int master_status_flags = fcntl(master, F_GETFL); - if (master_status_flags == -1) { - status = -errno; - ELOG("Failed to get master descriptor status flags: %s", strerror(errno)); - goto error; - } - if (fcntl(master, F_SETFL, master_status_flags | O_NONBLOCK) == -1) { - status = -errno; - ELOG("Failed to make master descriptor non-blocking: %s", strerror(errno)); - goto error; - } - - // Other jobs and providers should not get a copy of this file descriptor. - if (os_set_cloexec(master) == -1) { - status = -errno; - ELOG("Failed to set CLOEXEC on ptmx file descriptor"); - goto error; - } - - if (!proc->in.closed - && (status = set_duplicating_descriptor(master, &proc->in.uv.pipe))) { - goto error; - } - if (!proc->out.s.closed - && (status = set_duplicating_descriptor(master, &proc->out.s.uv.pipe))) { - goto error; - } - - ptyproc->tty_fd = master; - proc->pid = pid; - return 0; - -error: - close(master); - kill(pid, SIGKILL); - waitpid(pid, NULL, 0); - return status; -} - -const char *pty_process_tty_name(PtyProcess *ptyproc) -{ - return ptsname(ptyproc->tty_fd); -} - -void pty_process_resize(PtyProcess *ptyproc, uint16_t width, uint16_t height) - FUNC_ATTR_NONNULL_ALL -{ - ptyproc->winsize = (struct winsize){ height, width, 0, 0 }; - ioctl(ptyproc->tty_fd, TIOCSWINSZ, &ptyproc->winsize); -} - -void pty_process_close(PtyProcess *ptyproc) - FUNC_ATTR_NONNULL_ALL -{ - pty_process_close_master(ptyproc); - Process *proc = (Process *)ptyproc; - if (proc->internal_close_cb) { - proc->internal_close_cb(proc); - } -} - -void pty_process_close_master(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL -{ - if (ptyproc->tty_fd >= 0) { - close(ptyproc->tty_fd); - ptyproc->tty_fd = -1; - } -} - -void pty_process_teardown(Loop *loop) -{ - uv_signal_stop(&loop->children_watcher); -} - -static void init_child(PtyProcess *ptyproc) - FUNC_ATTR_NONNULL_ALL -{ -#if defined(HAVE__NSGETENVIRON) -# define environ (*_NSGetEnviron()) -#else - extern char **environ; -#endif - // New session/process-group. #6530 - setsid(); - - signal(SIGCHLD, SIG_DFL); - signal(SIGHUP, SIG_DFL); - signal(SIGINT, SIG_DFL); - signal(SIGQUIT, SIG_DFL); - signal(SIGTERM, SIG_DFL); - signal(SIGALRM, SIG_DFL); - - Process *proc = (Process *)ptyproc; - if (proc->cwd && os_chdir(proc->cwd) != 0) { - ELOG("chdir(%s) failed: %s", proc->cwd, strerror(errno)); - return; - } - - const char *prog = process_get_exepath(proc); - - assert(proc->env); - environ = tv_dict_to_env(proc->env); - execvp(prog, proc->argv); - ELOG("execvp(%s) failed: %s", prog, strerror(errno)); - - _exit(122); // 122 is EXEC_FAILED in the Vim source. -} - -static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL -{ - // Taken from pangoterm - termios->c_iflag = ICRNL|IXON; - termios->c_oflag = OPOST|ONLCR; -#ifdef TAB0 - termios->c_oflag |= TAB0; -#endif - termios->c_cflag = CS8|CREAD; - termios->c_lflag = ISIG|ICANON|IEXTEN|ECHO|ECHOE|ECHOK; - - // not using cfsetspeed, not available on all platforms - cfsetispeed(termios, 38400); - cfsetospeed(termios, 38400); - -#ifdef IUTF8 - termios->c_iflag |= IUTF8; -#endif -#ifdef NL0 - termios->c_oflag |= NL0; -#endif -#ifdef CR0 - termios->c_oflag |= CR0; -#endif -#ifdef BS0 - termios->c_oflag |= BS0; -#endif -#ifdef VT0 - termios->c_oflag |= VT0; -#endif -#ifdef FF0 - termios->c_oflag |= FF0; -#endif -#ifdef ECHOCTL - termios->c_lflag |= ECHOCTL; -#endif -#ifdef ECHOKE - termios->c_lflag |= ECHOKE; -#endif - - termios->c_cc[VINTR] = 0x1f & 'C'; - termios->c_cc[VQUIT] = 0x1f & '\\'; - termios->c_cc[VERASE] = 0x7f; - termios->c_cc[VKILL] = 0x1f & 'U'; - termios->c_cc[VEOF] = 0x1f & 'D'; - termios->c_cc[VEOL] = _POSIX_VDISABLE; - termios->c_cc[VEOL2] = _POSIX_VDISABLE; - termios->c_cc[VSTART] = 0x1f & 'Q'; - termios->c_cc[VSTOP] = 0x1f & 'S'; - termios->c_cc[VSUSP] = 0x1f & 'Z'; - termios->c_cc[VREPRINT] = 0x1f & 'R'; - termios->c_cc[VWERASE] = 0x1f & 'W'; - termios->c_cc[VLNEXT] = 0x1f & 'V'; - termios->c_cc[VMIN] = 1; - termios->c_cc[VTIME] = 0; -} - -static int set_duplicating_descriptor(int fd, uv_pipe_t *pipe) - FUNC_ATTR_NONNULL_ALL -{ - int status = 0; // zero or negative error code (libuv convention) - int fd_dup = dup(fd); - if (fd_dup < 0) { - status = -errno; - ELOG("Failed to dup descriptor %d: %s", fd, strerror(errno)); - return status; - } - - if (os_set_cloexec(fd_dup) == -1) { - status = -errno; - ELOG("Failed to set CLOEXEC on duplicate fd"); - goto error; - } - - status = uv_pipe_open(pipe, fd_dup); - if (status) { - ELOG("Failed to set pipe to descriptor %d: %s", - fd_dup, uv_strerror(status)); - goto error; - } - return status; - -error: - close(fd_dup); - return status; -} - -static void chld_handler(uv_signal_t *handle, int signum) -{ - int stat = 0; - int pid; - - Loop *loop = handle->loop->data; - - kl_iter(WatcherPtr, loop->children, current) { - Process *proc = (*current)->data; - do { - pid = waitpid(proc->pid, &stat, WNOHANG); - } while (pid < 0 && errno == EINTR); - - if (pid <= 0) { - continue; - } - - if (WIFEXITED(stat)) { - proc->status = WEXITSTATUS(stat); - } else if (WIFSIGNALED(stat)) { - proc->status = 128 + WTERMSIG(stat); - } - proc->internal_exit_cb(proc); - } -} - -PtyProcess pty_process_init(Loop *loop, void *data) -{ - PtyProcess rv; - rv.process = process_init(loop, kProcessTypePty, data); - rv.width = 80; - rv.height = 24; - rv.tty_fd = -1; - return rv; -} diff --git a/src/nvim/os/pty_process_unix.h b/src/nvim/os/pty_process_unix.h deleted file mode 100644 index 1a77ae5fd5..0000000000 --- a/src/nvim/os/pty_process_unix.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -// IWYU pragma: private, include "nvim/os/pty_process.h" - -#include -#include - -#include "nvim/event/defs.h" - -typedef struct { - Process process; - uint16_t width, height; - struct winsize winsize; - int tty_fd; -} PtyProcess; - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os/pty_process_unix.h.generated.h" -#endif diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c deleted file mode 100644 index 39c3966c1c..0000000000 --- a/src/nvim/os/pty_process_win.c +++ /dev/null @@ -1,440 +0,0 @@ -#include -#include -#include - -#include "nvim/ascii_defs.h" -#include "nvim/eval/typval.h" -#include "nvim/event/loop.h" -#include "nvim/log.h" -#include "nvim/mbyte.h" -#include "nvim/memory.h" -#include "nvim/os/os.h" -#include "nvim/os/pty_conpty_win.h" -#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; - - os_conpty_free(ptyproc->conpty); - // NB: pty_process_finish1() is called on a separate thread, - // but the timer only works properly if it's started by the main thread. - loop_schedule_fast(proc->loop, event_create(start_wait_eof_timer, ptyproc)); -} - -static void start_wait_eof_timer(void **argv) - FUNC_ATTR_NONNULL_ALL -{ - PtyProcess *ptyproc = (PtyProcess *)argv[0]; - - if (ptyproc->finish_wait != NULL) { - 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; - conpty_t *conpty_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; - wchar_t *env = NULL; - const char *emsg = NULL; - - assert(proc->err.s.closed); - - if (!os_has_conpty_working() || (conpty_object = os_conpty_init(&in_name, - &out_name, ptyproc->width, - ptyproc->height)) == NULL) { - status = UV_ENOSYS; - 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.s.closed) { - out_req = xmalloc(sizeof(uv_connect_t)); - uv_pipe_connect(out_req, - &proc->out.s.uv.pipe, - out_name, - pty_process_connect_cb); - } - - if (proc->cwd != NULL) { - status = utf8_to_utf16(proc->cwd, -1, &cwd); - if (status != 0) { - emsg = "utf8_to_utf16(proc->cwd) failed"; - goto cleanup; - } - } - - status = build_cmd_line(proc->argv, &cmd_line, - os_shell_is_cmdexe(proc->argv[0])); - if (status != 0) { - emsg = "build_cmd_line failed"; - goto cleanup; - } - - if (proc->env != NULL) { - status = build_env_block(proc->env, &env); - } - - if (status != 0) { - emsg = "build_env_block failed"; - goto cleanup; - } - - if (!os_conpty_spawn(conpty_object, - &process_handle, - NULL, - cmd_line, - cwd, - env)) { - emsg = "os_conpty_spawn failed"; - status = (int)GetLastError(); - goto cleanup; - } - proc->pid = (int)GetProcessId(process_handle); - - uv_timer_init(&proc->loop->uv, &ptyproc->wait_eof_timer); - ptyproc->wait_eof_timer.data = (void *)ptyproc; - 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->conpty = conpty_object; - ptyproc->process_handle = process_handle; - conpty_object = NULL; - process_handle = NULL; - -cleanup: - if (status) { - // In the case of an error of MultiByteToWideChar or CreateProcessW. - ELOG("pty_process_spawn(%s): %s: error code: %d", - proc->argv[0], emsg, status); - status = os_translate_sys_error(status); - } - os_conpty_free(conpty_object); - xfree(in_name); - xfree(out_name); - if (process_handle != NULL) { - CloseHandle(process_handle); - } - xfree(in_req); - xfree(out_req); - xfree(cmd_line); - xfree(env); - xfree(cwd); - return status; -} - -const char *pty_process_tty_name(PtyProcess *ptyproc) -{ - return "?"; -} - -void pty_process_resize(PtyProcess *ptyproc, uint16_t width, uint16_t height) - FUNC_ATTR_NONNULL_ALL -{ - os_conpty_set_size(ptyproc->conpty, width, height); -} - -void pty_process_close(PtyProcess *ptyproc) - FUNC_ATTR_NONNULL_ALL -{ - Process *proc = (Process *)ptyproc; - - pty_process_close_master(ptyproc); - - if (ptyproc->finish_wait != NULL) { - UnregisterWaitEx(ptyproc->finish_wait, NULL); - ptyproc->finish_wait = NULL; - uv_close((uv_handle_t *)&ptyproc->wait_eof_timer, NULL); - } - if (ptyproc->process_handle != NULL) { - CloseHandle(ptyproc->process_handle); - ptyproc->process_handle = NULL; - } - - if (proc->internal_close_cb) { - proc->internal_close_cb(proc); - } -} - -void pty_process_close_master(PtyProcess *ptyproc) - FUNC_ATTR_NONNULL_ALL -{ -} - -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; - - assert(ptyproc->finish_wait != NULL); - if (proc->out.s.closed || proc->out.did_eof || !uv_is_readable(proc->out.s.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; - - DWORD exit_code = 0; - GetExitCodeProcess(ptyproc->process_handle, &exit_code); - proc->status = proc->exit_signal ? 128 + proc->exit_signal : (int)exit_code; - - 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 built 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; - QUEUE *q; - QUEUE_FOREACH(q, &args_q, { - ArgNode *arg_node = QUEUE_DATA(q, ArgNode, node); - xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len); - QUEUE_REMOVE(q); - xfree(arg_node->arg); - xfree(arg_node); - if (!QUEUE_EMPTY(&args_q)) { - xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len); - } - }) - - int result = utf8_to_utf16(utf8_cmd_line, -1, 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--; - } -} - -typedef struct EnvNode { - wchar_t *str; - size_t len; - QUEUE node; -} EnvNode; - -/// Build the environment block to pass to CreateProcessW. -/// -/// @param[in] denv Dict of environment name/value pairs -/// @param[out] env Allocated environment block -/// -/// @returns zero on success or error code of MultiByteToWideChar function. -static int build_env_block(dict_T *denv, wchar_t **env_block) -{ - const size_t denv_size = (size_t)tv_dict_len(denv); - size_t env_block_len = 0; - int rc = 0; - char **env = tv_dict_to_env(denv); - - QUEUE *q; - QUEUE env_q; - QUEUE_INIT(&env_q); - // Convert env vars to wchar_t and calculate how big the final env block - // needs to be - for (size_t i = 0; i < denv_size; i++) { - EnvNode *env_node = xmalloc(sizeof(*env_node)); - rc = utf8_to_utf16(env[i], -1, &env_node->str); - if (rc != 0) { - goto cleanup; - } - env_node->len = wcslen(env_node->str) + 1; - env_block_len += env_node->len; - QUEUE_INSERT_TAIL(&env_q, &env_node->node); - } - - // Additional NUL after the final entry - env_block_len++; - - *env_block = xmalloc(sizeof(**env_block) * env_block_len); - wchar_t *pos = *env_block; - - QUEUE_FOREACH(q, &env_q, { - EnvNode *env_node = QUEUE_DATA(q, EnvNode, node); - memcpy(pos, env_node->str, env_node->len * sizeof(*pos)); - pos += env_node->len; - }) - - *pos = L'\0'; - -cleanup: - q = QUEUE_HEAD(&env_q); - while (q != &env_q) { - QUEUE *next = q->next; - EnvNode *env_node = QUEUE_DATA(q, EnvNode, node); - XFREE_CLEAR(env_node->str); - QUEUE_REMOVE(q); - xfree(env_node); - q = next; - } - - return rc; -} - -PtyProcess pty_process_init(Loop *loop, void *data) -{ - PtyProcess rv; - rv.process = process_init(loop, kProcessTypePty, data); - rv.width = 80; - rv.height = 24; - rv.conpty = NULL; - rv.finish_wait = NULL; - rv.process_handle = NULL; - return rv; -} diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h deleted file mode 100644 index 3528f6bfe5..0000000000 --- a/src/nvim/os/pty_process_win.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once -// IWYU pragma: private, include "nvim/os/pty_process.h" - -#include - -#include "nvim/event/process.h" -#include "nvim/lib/queue_defs.h" -#include "nvim/os/pty_conpty_win.h" - -typedef struct pty_process { - Process process; - uint16_t width, height; - conpty_t *conpty; - HANDLE finish_wait; - HANDLE process_handle; - uv_timer_t wait_eof_timer; -} PtyProcess; - -// Structure used by build_cmd_line() -typedef struct arg_node { - char *arg; // pointer to argument. - QUEUE node; // QUEUE structure. -} ArgNode; - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os/pty_process_win.h.generated.h" -#endif diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 0b082c164d..a1ec9449df 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -14,10 +14,10 @@ #include "nvim/eval.h" #include "nvim/eval/typval_defs.h" #include "nvim/event/defs.h" -#include "nvim/event/libuv_process.h" +#include "nvim/event/libuv_proc.h" #include "nvim/event/loop.h" #include "nvim/event/multiqueue.h" -#include "nvim/event/process.h" +#include "nvim/event/proc.h" #include "nvim/event/rstream.h" #include "nvim/event/stream.h" #include "nvim/event/wstream.h" @@ -872,12 +872,12 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu char prog[MAXPATHL]; xstrlcpy(prog, argv[0], MAXPATHL); - LibuvProcess uvproc = libuv_process_init(&main_loop, &buf); - Process *proc = &uvproc.process; + LibuvProc uvproc = libuv_proc_init(&main_loop, &buf); + Proc *proc = &uvproc.proc; MultiQueue *events = multiqueue_new_child(main_loop.events); proc->events = events; proc->argv = argv; - int status = process_spawn(proc, has_input, true, true); + int status = proc_spawn(proc, has_input, true, true); if (status) { loop_poll_events(&main_loop, 0); // Failed, probably 'shell' is not executable. @@ -910,7 +910,7 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu if (!wstream_write(&proc->in, input_buffer)) { // couldn't write, stop the process and tell the user about it - process_stop(proc); + proc_stop(proc); return -1; } // close the input stream after everything is written @@ -927,7 +927,7 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu msg_no_more = true; lines_left = -1; } - int exitcode = process_wait(proc, -1, NULL); + int exitcode = proc_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); -- cgit From 83a7d97d64a6186d6c4ec9ff290997a7239cbe76 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 18 Sep 2024 01:26:04 -0700 Subject: fix(coverity): CID 509571 Uninitialized variables #30395 /src/nvim/os/pty_proc_unix.c: 416 in pty_proc_init() 410 { 411 PtyProc rv; 412 rv.proc = proc_init(loop, kProcTypePty, data); 413 rv.width = 80; 414 rv.height = 24; 415 rv.tty_fd = -1; >>> CID 509571: Uninitialized variables (UNINIT) >>> Using uninitialized value "rv". Field "rv.winsize" is uninitialized. 416 return rv; --- src/nvim/os/pty_proc_unix.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/pty_proc_unix.c b/src/nvim/os/pty_proc_unix.c index 9e9303ed48..e629b328fd 100644 --- a/src/nvim/os/pty_proc_unix.c +++ b/src/nvim/os/pty_proc_unix.c @@ -408,7 +408,7 @@ static void chld_handler(uv_signal_t *handle, int signum) PtyProc pty_proc_init(Loop *loop, void *data) { - PtyProc rv; + PtyProc rv = { 0 }; rv.proc = proc_init(loop, kProcTypePty, data); rv.width = 80; rv.height = 24; -- cgit From 737f58e23230ea14f1648ac1fc7f442ea0f8563c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 20 Sep 2024 07:34:50 +0200 Subject: refactor(api)!: rename Dictionary => Dict In the api_info() output: :new|put =map(filter(api_info().functions, '!has_key(v:val,''deprecated_since'')'), 'v:val') ... {'return_type': 'ArrayOf(Integer, 2)', 'name': 'nvim_win_get_position', 'method': v:true, 'parameters': [['Window', 'window']], 'since': 1} The `ArrayOf(Integer, 2)` return type didn't break clients when we added it, which is evidence that clients don't use the `return_type` field, thus renaming Dictionary => Dict in api_info() is not (in practice) a breaking change. --- src/nvim/os/proc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/proc.c b/src/nvim/os/proc.c index 1670e469ee..053f5f3ba0 100644 --- a/src/nvim/os/proc.c +++ b/src/nvim/os/proc.c @@ -230,9 +230,9 @@ int os_proc_children(int ppid, int **proc_list, size_t *proc_count) /// /// @param pid Process to inspect. /// @return Map of process properties, empty on error. -Dictionary os_proc_info(int pid, Arena *arena) +Dict os_proc_info(int pid, Arena *arena) { - Dictionary pinfo = ARRAY_DICT_INIT; + Dict pinfo = ARRAY_DICT_INIT; PROCESSENTRY32 pe; // Snapshot of all processes. This is used instead of: -- cgit From 76163590f0b1a39e281446b6b6b17d00b0dcae15 Mon Sep 17 00:00:00 2001 From: bfredl Date: Sat, 28 Sep 2024 11:56:08 +0200 Subject: refactor(event): change last use of klist to kvec loop->children might have been a linked list because used to be modified in place while looped over. However the loops that exists rather schedules events to be processed later, outside of the loop, so this can not happen anymore. When a linked list is otherwise useful it is better to use lib/queue_defs.h which defines an _intrusive_ linked list (i e it doesn't need to do allocations for list items like klist ). --- src/nvim/os/pty_proc_unix.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/pty_proc_unix.c b/src/nvim/os/pty_proc_unix.c index e629b328fd..3bca065d2d 100644 --- a/src/nvim/os/pty_proc_unix.c +++ b/src/nvim/os/pty_proc_unix.c @@ -30,7 +30,6 @@ #endif #include "auto/config.h" -#include "klib/klist.h" #include "nvim/eval/typval.h" #include "nvim/event/defs.h" #include "nvim/event/loop.h" @@ -387,8 +386,8 @@ static void chld_handler(uv_signal_t *handle, int signum) Loop *loop = handle->loop->data; - kl_iter(WatcherPtr, loop->children, current) { - Proc *proc = (*current)->data; + for (size_t i = 0; i < kv_size(loop->children); i++) { + Proc *proc = kv_A(loop->children, i); do { pid = waitpid(proc->pid, &stat, WNOHANG); } while (pid < 0 && errno == EINTR); -- cgit From 184d5e75434f52170df930dcb6928014d03a7081 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 3 Oct 2024 10:34:55 +0800 Subject: refactor: fix incorrect use of enum (#30631) --- src/nvim/os/shell.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'src/nvim/os') diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index a1ec9449df..efcdee9c8b 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -115,7 +115,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in size_t len; char *p; char *extra_shell_arg = NULL; - ShellOpts shellopts = kShellOptExpand | kShellOptSilent; + int shellopts = kShellOptExpand | kShellOptSilent; int j; char *tempname; #define STYLE_ECHO 0 // use "echo", the default @@ -659,7 +659,7 @@ char *shell_argv_to_str(char **const argv) /// @param extra_args Extra arguments to the shell, or NULL. /// /// @return shell command exit code -int os_call_shell(char *cmd, ShellOpts opts, char *extra_args) +int os_call_shell(char *cmd, int opts, char *extra_args) { StringBuilder input = KV_INITIAL_VALUE; char *output = NULL; @@ -714,8 +714,10 @@ int os_call_shell(char *cmd, ShellOpts opts, char *extra_args) /// os_call_shell() wrapper. Handles 'verbose', :profile, and v:shell_error. /// Invalidates cached tags. /// +/// @param opts a combination of ShellOpts flags +/// /// @return shell command exit code -int call_shell(char *cmd, ShellOpts opts, char *extra_shell_arg) +int call_shell(char *cmd, int opts, char *extra_shell_arg) { int retval; proftime_T wait_time; @@ -759,7 +761,7 @@ int call_shell(char *cmd, ShellOpts opts, char *extra_shell_arg) /// @param ret_len length of the stdout /// /// @return an allocated string, or NULL for error. -char *get_cmd_output(char *cmd, char *infile, ShellOpts flags, size_t *ret_len) +char *get_cmd_output(char *cmd, char *infile, int flags, size_t *ret_len) { char *buffer = NULL; -- cgit