diff options
-rw-r--r-- | src/nvim/ex_cmds2.c | 9 | ||||
-rw-r--r-- | src/nvim/fileio.c | 13 | ||||
-rw-r--r-- | src/nvim/memfile.c | 7 | ||||
-rw-r--r-- | src/nvim/memline.c | 9 | ||||
-rw-r--r-- | src/nvim/os/fs.c | 29 | ||||
-rw-r--r-- | src/nvim/os/pty_process_unix.c | 21 | ||||
-rw-r--r-- | src/nvim/testdir/test_undo.vim | 207 | ||||
-rw-r--r-- | src/nvim/version.c | 2 | ||||
-rw-r--r-- | test/functional/core/job_spec.lua | 24 |
9 files changed, 285 insertions, 36 deletions
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 048323b137..36f50ae7a2 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -2692,14 +2692,7 @@ static FILE *fopen_noinh_readbin(char *filename) return NULL; } -#ifdef HAVE_FD_CLOEXEC - { - int fdflags = fcntl(fd_tmp, F_GETFD); - if (fdflags >= 0 && (fdflags & FD_CLOEXEC) == 0) { - (void)fcntl(fd_tmp, F_SETFD, fdflags | FD_CLOEXEC); - } - } -#endif + (void)os_set_cloexec(fd_tmp); return fdopen(fd_tmp, READBIN); } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 040df707de..9c64be6d0c 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -1742,16 +1742,11 @@ failed: } # endif - if (!read_buffer && !read_stdin) - close(fd); /* errors are ignored */ -#ifdef HAVE_FD_CLOEXEC - else { - int fdflags = fcntl(fd, F_GETFD); - if (fdflags >= 0 && (fdflags & FD_CLOEXEC) == 0) { - (void)fcntl(fd, F_SETFD, fdflags | FD_CLOEXEC); - } + if (!read_buffer && !read_stdin) { + close(fd); // errors are ignored + } else { + (void)os_set_cloexec(fd); } -#endif xfree(buffer); if (read_stdin) { diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c index 43412e3916..5d6639c4d0 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -909,12 +909,7 @@ static bool mf_do_open(memfile_T *mfp, char_u *fname, int flags) return false; } -#ifdef HAVE_FD_CLOEXEC - int fdflags = fcntl(mfp->mf_fd, F_GETFD); - if (fdflags >= 0 && (fdflags & FD_CLOEXEC) == 0) { - (void)fcntl(mfp->mf_fd, F_SETFD, fdflags | FD_CLOEXEC); - } -#endif + (void)os_set_cloexec(mfp->mf_fd); #ifdef HAVE_SELINUX mch_copy_sec(fname, mfp->mf_fname); #endif diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 1a315fce8b..f9d3751390 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -439,14 +439,7 @@ void ml_setname(buf_T *buf) EMSG(_("E301: Oops, lost the swap file!!!")); return; } -#ifdef HAVE_FD_CLOEXEC - { - int fdflags = fcntl(mfp->mf_fd, F_GETFD); - if (fdflags >= 0 && (fdflags & FD_CLOEXEC) == 0) { - (void)fcntl(mfp->mf_fd, F_SETFD, fdflags | FD_CLOEXEC); - } - } -#endif + (void)os_set_cloexec(mfp->mf_fd); } if (!success) EMSG(_("E302: Could not rename swap file")); diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index e930561234..af60e1b7af 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -391,6 +391,35 @@ int os_open(const char* path, int flags, int mode) return r; } +/// Sets file descriptor `fd` to close-on-exec. +// +// @return -1 if failed to set, 0 otherwise. +int os_set_cloexec(const int fd) +{ +#ifdef HAVE_FD_CLOEXEC + int e; + int fdflags = fcntl(fd, F_GETFD); + if (fdflags < 0) { + e = errno; + ELOG("Failed to get flags on descriptor %d: %s", fd, strerror(e)); + errno = e; + return -1; + } + if ((fdflags & FD_CLOEXEC) == 0 + && fcntl(fd, F_SETFD, fdflags | FD_CLOEXEC) < 0) { + e = errno; + ELOG("Failed to set CLOEXEC on descriptor %d: %s", fd, strerror(e)); + errno = e; + return -1; + } + return 0; +#endif + + // No FD_CLOEXEC flag. On Windows, the file should have been opened with + // O_NOINHERIT anyway. + return -1; +} + /// Close a file /// /// @return 0 or libuv error code on failure. diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index f5ba0c2612..71a5e13de5 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -73,6 +73,13 @@ int pty_process_spawn(PtyProcess *ptyproc) 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 && (status = set_duplicating_descriptor(master, &proc->in->uv.pipe))) { goto error; @@ -215,14 +222,24 @@ static int set_duplicating_descriptor(int fd, uv_pipe_t *pipe) 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)); - close(fd_dup); - return status; + goto error; } return status; + +error: + close(fd_dup); + return status; } static void chld_handler(uv_signal_t *handle, int signum) diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim new file mode 100644 index 0000000000..ff8ec67b0b --- /dev/null +++ b/src/nvim/testdir/test_undo.vim @@ -0,0 +1,207 @@ +" Tests for the undo tree. +" Since this script is sourced we need to explicitly break changes up in +" undo-able pieces. Do that by setting 'undolevels'. +" Also tests :earlier and :later. + +func Test_undotree() + exe "normal Aabc\<Esc>" + set ul=100 + exe "normal Adef\<Esc>" + set ul=100 + undo + let d = undotree() + call assert_true(d.seq_last > 0) + call assert_true(d.seq_cur > 0) + call assert_true(d.seq_cur < d.seq_last) + call assert_true(len(d.entries) > 0) + " TODO: check more members of d + + w! Xtest + call assert_equal(d.save_last + 1, undotree().save_last) + call delete('Xtest') + bwipe Xtest +endfunc + +func FillBuffer() + for i in range(1,13) + put=i + " Set 'undolevels' to split undo. + exe "setg ul=" . &g:ul + endfor +endfunc + +func Test_global_local_undolevels() + new one + set undolevels=5 + call FillBuffer() + " will only undo the last 5 changes, end up with 13 - (5 + 1) = 7 lines + earlier 10 + call assert_equal(5, &g:undolevels) + call assert_equal(-123456, &l:undolevels) + call assert_equal('7', getline('$')) + + new two + setlocal undolevels=2 + call FillBuffer() + " will only undo the last 2 changes, end up with 13 - (2 + 1) = 10 lines + earlier 10 + call assert_equal(5, &g:undolevels) + call assert_equal(2, &l:undolevels) + call assert_equal('10', getline('$')) + + setlocal ul=10 + call assert_equal(5, &g:undolevels) + call assert_equal(10, &l:undolevels) + + " Setting local value in "two" must not change local value in "one" + wincmd p + call assert_equal(5, &g:undolevels) + call assert_equal(-123456, &l:undolevels) + + new three + setglobal ul=50 + call assert_equal(50, &g:undolevels) + call assert_equal(-123456, &l:undolevels) + + " Drop created windows + set ul& + new + only! +endfunc + +func BackOne(expected) + call feedkeys('g-', 'xt') + call assert_equal(a:expected, getline(1)) +endfunc + +" Test is disabled: Nvim does not support test_settime(). +func Test_undo_del_chars_skipped() + return + + " Setup a buffer without creating undo entries + new + set ul=-1 + call setline(1, ['123-456']) + set ul=100 + 1 + call test_settime(100) + + " Delete three characters and undo with g- + call feedkeys('x', 'xt') + call feedkeys('x', 'xt') + call feedkeys('x', 'xt') + call assert_equal('-456', getline(1)) + call BackOne('3-456') + call BackOne('23-456') + call BackOne('123-456') + call assert_fails("BackOne('123-456')") + + :" Delete three other characters and go back in time with g- + call feedkeys('$x', 'xt') + call feedkeys('x', 'xt') + call feedkeys('x', 'xt') + call assert_equal('123-', getline(1)) + call test_settime(101) + + call BackOne('123-4') + call BackOne('123-45') + " skips '123-456' because it's older + call BackOne('-456') + call BackOne('3-456') + call BackOne('23-456') + call BackOne('123-456') + call assert_fails("BackOne('123-456')") + normal 10g+ + call assert_equal('123-', getline(1)) + + :" Jump two seconds and go some seconds forward and backward + call test_settime(103) + call feedkeys("Aa\<Esc>", 'xt') + call feedkeys("Ab\<Esc>", 'xt') + call feedkeys("Ac\<Esc>", 'xt') + call assert_equal('123-abc', getline(1)) + earlier 1s + call assert_equal('123-', getline(1)) + earlier 3s + call assert_equal('123-456', getline(1)) + later 1s + call assert_equal('123-', getline(1)) + later 1h + call assert_equal('123-abc', getline(1)) + + close! +endfunc + +func Test_undojoin() + new + call feedkeys("Goaaaa\<Esc>", 'xt') + call feedkeys("obbbb\<Esc>", 'xt') + call assert_equal(['aaaa', 'bbbb'], getline(2, '$')) + call feedkeys("u", 'xt') + call assert_equal(['aaaa'], getline(2, '$')) + call feedkeys("obbbb\<Esc>", 'xt') + undojoin + " Note: next change must not be as if typed + call feedkeys("occcc\<Esc>", 'x') + call assert_equal(['aaaa', 'bbbb', 'cccc'], getline(2, '$')) + call feedkeys("u", 'xt') + call assert_equal(['aaaa'], getline(2, '$')) + close! +endfunc + +func Test_undo_write() + split Xtest + call feedkeys("ione one one\<Esc>", 'xt') + w! + call feedkeys("otwo\<Esc>", 'xt') + call feedkeys("otwo\<Esc>", 'xt') + w + call feedkeys("othree\<Esc>", 'xt') + call assert_equal(['one one one', 'two', 'two', 'three'], getline(1, '$')) + earlier 1f + call assert_equal(['one one one', 'two', 'two'], getline(1, '$')) + earlier 1f + call assert_equal(['one one one'], getline(1, '$')) + earlier 1f + call assert_equal([''], getline(1, '$')) + later 1f + call assert_equal(['one one one'], getline(1, '$')) + later 1f + call assert_equal(['one one one', 'two', 'two'], getline(1, '$')) + later 1f + call assert_equal(['one one one', 'two', 'two', 'three'], getline(1, '$')) + + close! + call delete('Xtest') + bwipe! Xtest +endfunc + +func Test_insert_expr() + new + " calling setline() triggers undo sync + call feedkeys("oa\<Esc>", 'xt') + call feedkeys("ob\<Esc>", 'xt') + set ul=100 + call feedkeys("o1\<Esc>a2\<C-R>=setline('.','1234')\<CR>\<CR>\<Esc>", 'x') + call assert_equal(['a', 'b', '120', '34'], getline(2, '$')) + call feedkeys("u", 'x') + call assert_equal(['a', 'b', '12'], getline(2, '$')) + call feedkeys("u", 'x') + call assert_equal(['a', 'b'], getline(2, '$')) + + call feedkeys("oc\<Esc>", 'xt') + set ul=100 + call feedkeys("o1\<Esc>a2\<C-R>=setline('.','1234')\<CR>\<CR>\<Esc>", 'x') + call assert_equal(['a', 'b', 'c', '120', '34'], getline(2, '$')) + call feedkeys("u", 'x') + call assert_equal(['a', 'b', 'c', '12'], getline(2, '$')) + + call feedkeys("od\<Esc>", 'xt') + set ul=100 + call feedkeys("o1\<Esc>a2\<C-R>=string(123)\<CR>\<Esc>", 'x') + call assert_equal(['a', 'b', 'c', '12', 'd', '12123'], getline(2, '$')) + call feedkeys("u", 'x') + call assert_equal(['a', 'b', 'c', '12', 'd'], getline(2, '$')) + + close! +endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index 64bda20198..7a6c41c738 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -327,7 +327,7 @@ static int included_patches[] = { // 2116 NA // 2115 NA // 2114 NA - // 2113, + 2113, 2112, // 2111 NA 2110, diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index b551adb8db..e442c2a317 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -1,7 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) -local clear, eq, eval, execute, feed, insert, neq, next_msg, nvim, +local clear, eq, eval, exc_exec, execute, feed, insert, neq, next_msg, nvim, nvim_dir, ok, source, write_file, mkdir, rmdir = helpers.clear, - helpers.eq, helpers.eval, helpers.execute, helpers.feed, + helpers.eq, helpers.eval, helpers.exc_exec, helpers.execute, helpers.feed, helpers.insert, helpers.neq, helpers.next_message, helpers.nvim, helpers.nvim_dir, helpers.ok, helpers.source, helpers.write_file, helpers.mkdir, helpers.rmdir @@ -622,6 +622,26 @@ describe('jobs', function() msg = (msg[2] == 'stdout') and next_msg() or msg -- Skip stdout, if any. eq({'notification', 'exit', {0, 42}}, msg) end) + + it('jobstart() does not keep ptmx file descriptor open', function() + -- Start another job (using libuv) + command('let g:job_opts.pty = 0') + local other_jobid = eval("jobstart(['cat', '-'], g:job_opts)") + local other_pid = eval('jobpid(' .. other_jobid .. ')') + + -- Other job doesn't block first job from recieving SIGHUP on jobclose() + command('call jobclose(j)') + -- Have to wait so that the SIGHUP can be processed by tty-test on time. + -- Can't wait for the next message in case this test fails, if it fails + -- there won't be any more messages, and the test would hang. + helpers.sleep(100) + local err = exc_exec('call jobpid(j)') + eq('Vim(call):E900: Invalid job id', err) + + -- cleanup + eq(other_pid, eval('jobpid(' .. other_jobid .. ')')) + command('call jobstop(' .. other_jobid .. ')') + end) end) end) |