aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/nvim/ex_cmds2.c9
-rw-r--r--src/nvim/fileio.c13
-rw-r--r--src/nvim/memfile.c7
-rw-r--r--src/nvim/memline.c9
-rw-r--r--src/nvim/os/fs.c29
-rw-r--r--src/nvim/os/pty_process_unix.c21
-rw-r--r--src/nvim/testdir/test_undo.vim207
-rw-r--r--src/nvim/version.c2
-rw-r--r--test/functional/core/job_spec.lua24
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)