From ce0fddf5ae334f0c79dcd95b379999e11df1486b Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 9 Mar 2023 08:07:36 -0500 Subject: feat: try to recover from missing tempdir #22573 Problem: If vim_tempdir mysteriously goes missing (typically by "antivirus" on Windows), any plugins using tempname() will be broken for the rest of the session. #1432 #9833 https://groups.google.com/g/vim_use/c/ef55jNm5czI Steps: mkdir foo TMPDIR=./foo nvim :echo tempname() !rm -r foo :echo tempname() tempname() still uses the foo path even though it was deleted. Solution: - Don't assume that vim_tempdir exists. - If it goes missing once, retry vim_mktempdir and log (silently) an error. - If it goes missing again, retry vim_mktempdir and show an error. Rejected in Vim for performance reasons: https://groups.google.com/g/vim_use/c/qgRob9SWDv8/m/FAOFVVcDTv0J https://groups.google.com/g/vim_dev/c/cogp-Vye4oo/m/d_SVFXBbnnoJ But, logging shows that `vim_gettempdir` is not called frequently. Fixes #1432 Fixes #9833 Fixes #11250 Related: stdpath("run") f50135a32e11c535e1dc3a8e9460c5b4e640ee86 --- test/functional/core/fileio_spec.lua | 64 +++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 12 deletions(-) (limited to 'test/functional') diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua index 4e9891a4de..153b53dce2 100644 --- a/test/functional/core/fileio_spec.lua +++ b/test/functional/core/fileio_spec.lua @@ -15,6 +15,7 @@ local request = helpers.request local retry = helpers.retry local rmdir = helpers.rmdir local matches = helpers.matches +local meths = helpers.meths local mkdir = helpers.mkdir local sleep = helpers.sleep local read_file = helpers.read_file @@ -261,13 +262,13 @@ end) describe('tmpdir', function() local tmproot_pat = [=[.*[/\\]nvim%.[^/\\]+]=] local testlog = 'Xtest_tmpdir_log' - local faketmp + local os_tmpdir before_each(function() -- Fake /tmp dir so that we can mess it up. - faketmp = tmpname() - os.remove(faketmp) - mkdir(faketmp) + os_tmpdir = tmpname() + os.remove(os_tmpdir) + mkdir(os_tmpdir) end) after_each(function() @@ -275,16 +276,21 @@ describe('tmpdir', function() os.remove(testlog) end) - it('failure modes', function() - clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=faketmp, } }) - assert_nolog('tempdir is not a directory', testlog) - assert_nolog('tempdir has invalid permissions', testlog) - + local function get_tmproot() -- Tempfiles typically look like: "…/nvim./xxx/0". -- - "…/nvim./xxx/" is the per-process tmpdir, not shared with other Nvims. -- - "…/nvim./" is the tmpdir root, shared by all Nvims (normally). local tmproot = (funcs.tempname()):match(tmproot_pat) ok(tmproot:len() > 4, 'tmproot like "nvim.foo"', tmproot) + return tmproot + end + + it('failure modes', function() + clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=os_tmpdir, } }) + assert_nolog('tempdir is not a directory', testlog) + assert_nolog('tempdir has invalid permissions', testlog) + + local tmproot = get_tmproot() -- Test how Nvim handles invalid tmpdir root (by hostile users or accidents). -- @@ -292,7 +298,7 @@ describe('tmpdir', function() expect_exit(command, ':qall!') rmdir(tmproot) write_file(tmproot, '') -- Not a directory, vim_mktempdir() should skip it. - clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=faketmp, } }) + clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=os_tmpdir, } }) matches(tmproot_pat, funcs.stdpath('run')) -- Tickle vim_mktempdir(). -- Assert that broken tmpdir root was handled. assert_log('tempdir root not a directory', testlog, 100) @@ -303,18 +309,52 @@ describe('tmpdir', function() os.remove(tmproot) mkdir(tmproot) funcs.setfperm(tmproot, 'rwxr--r--') -- Invalid permissions, vim_mktempdir() should skip it. - clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=faketmp, } }) + clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=os_tmpdir, } }) matches(tmproot_pat, funcs.stdpath('run')) -- Tickle vim_mktempdir(). -- Assert that broken tmpdir root was handled. assert_log('tempdir root has invalid permissions', testlog, 100) end) it('too long', function() - local bigname = ('%s/%s'):format(faketmp, ('x'):rep(666)) + local bigname = ('%s/%s'):format(os_tmpdir, ('x'):rep(666)) mkdir(bigname) clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=bigname, } }) matches(tmproot_pat, funcs.stdpath('run')) -- Tickle vim_mktempdir(). local len = (funcs.tempname()):len() ok(len > 4 and len < 256, '4 < len < 256', tostring(len)) end) + + it('disappeared #1432', function() + clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=os_tmpdir, } }) + assert_nolog('tempdir disappeared', testlog) + + local function rm_tmpdir() + local tmpname1 = funcs.tempname() + local tmpdir1 = funcs.fnamemodify(tmpname1, ':h') + eq(funcs.stdpath('run'), tmpdir1) + + rmdir(tmpdir1) + retry(nil, 1000, function() + eq(0, funcs.isdirectory(tmpdir1)) + end) + local tmpname2 = funcs.tempname() + local tmpdir2 = funcs.fnamemodify(tmpname2, ':h') + neq(tmpdir1, tmpdir2) + end + + -- Your antivirus hates you... + rm_tmpdir() + assert_log('tempdir disappeared', testlog, 100) + funcs.tempname() + funcs.tempname() + funcs.tempname() + eq('', meths.get_vvar('errmsg')) + rm_tmpdir() + funcs.tempname() + funcs.tempname() + funcs.tempname() + eq('E5431: tempdir disappeared (2 times)', meths.get_vvar('errmsg')) + rm_tmpdir() + eq('E5431: tempdir disappeared (3 times)', meths.get_vvar('errmsg')) + end) end) -- cgit