mirror of
https://github.com/neovim/neovim.git
synced 2026-08-29 10:31:48 +00:00
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") f50135a32e
This commit is contained in:
@@ -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.<user>/xxx/0".
|
||||
-- - "…/nvim.<user>/xxx/" is the per-process tmpdir, not shared with other Nvims.
|
||||
-- - "…/nvim.<user>/" 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)
|
||||
|
||||
Reference in New Issue
Block a user