mirror of
https://github.com/neovim/neovim.git
synced 2026-07-13 21:00:42 +00:00
fix(vim.fs): fs.dir() may return nil "type" on some filesystems #39749
Problem:
Currently, only some filesystems (Btrfs, ext2, ext3, ext4) have full
support of accessing the `dirent` entry-type. On other filesystems,
`uv.fs_scandir_next` may return `nil` for an existing but unsupported
entry-type.
This means consumers (such as `fs.dir()`), cannot know if `nil` means
"non-existent" or "unsupported".
Solution:
Fall back to `uv.fs_lstat` when `etype` is `nil`; return "unknown" if it
fails.
(cherry picked from commit 4b5f026ac9)
This commit is contained in:
committed by
github-actions[bot]
parent
8fac963f65
commit
b9d39f5bb2
@@ -163,6 +163,29 @@ function M.joinpath(...)
|
|||||||
return (path:gsub(iswin and '[/\\][/\\]*' or '//+', '/'))
|
return (path:gsub(iswin and '[/\\][/\\]*' or '//+', '/'))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Wrapper around `uv.fs_scandir_next()` that ensures a file type is returned.
|
||||||
|
---
|
||||||
|
--- @param fs uv.uv_fs_t
|
||||||
|
--- @param path string
|
||||||
|
--- @return string?
|
||||||
|
--- @return string?
|
||||||
|
local function fs_scandir_next(fs, path)
|
||||||
|
-- use uv.fs_lstat instead of uv.fs_stat to avoid descending into a symlink entry as a directory/file
|
||||||
|
local name, etype = uv.fs_scandir_next(fs)
|
||||||
|
|
||||||
|
if not name then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if etype == nil then
|
||||||
|
local stat = vim.uv.fs_lstat(M.joinpath(path, name))
|
||||||
|
-- Workaround #39612 https://github.com/luvit/luv/issues/660
|
||||||
|
etype = stat and stat.type or 'unknown'
|
||||||
|
end
|
||||||
|
|
||||||
|
return name, etype
|
||||||
|
end
|
||||||
|
|
||||||
--- @class vim.fs.dir.Opts
|
--- @class vim.fs.dir.Opts
|
||||||
--- @inlinedoc
|
--- @inlinedoc
|
||||||
---
|
---
|
||||||
@@ -206,7 +229,7 @@ function M.dir(path, opts)
|
|||||||
if not fs then
|
if not fs then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
return uv.fs_scandir_next(fs)
|
return fs_scandir_next(fs, path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -219,7 +242,7 @@ function M.dir(path, opts)
|
|||||||
local dir = level == 1 and dir0 or M.joinpath(path, dir0)
|
local dir = level == 1 and dir0 or M.joinpath(path, dir0)
|
||||||
local fs = uv.fs_scandir(dir)
|
local fs = uv.fs_scandir(dir)
|
||||||
while fs do
|
while fs do
|
||||||
local name, t = uv.fs_scandir_next(fs)
|
local name, t = fs_scandir_next(fs, dir)
|
||||||
if not name then
|
if not name then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -242,6 +242,41 @@ describe('vim.fs', function()
|
|||||||
end)
|
end)
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
describe('fs_scandir_next fallback', function()
|
||||||
|
before_each(function()
|
||||||
|
mkdir('testdir')
|
||||||
|
t.write_file('testdir/test.txt', 'test file')
|
||||||
|
end)
|
||||||
|
|
||||||
|
after_each(function()
|
||||||
|
rmdir('testdir')
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('falls back to fs_lstat when fs_scandir_next returns nil type', function()
|
||||||
|
local result = n.exec_lua([[
|
||||||
|
local orig = vim.uv.fs_scandir_next
|
||||||
|
|
||||||
|
vim.uv.fs_scandir_next = function(fs)
|
||||||
|
local name = orig(fs)
|
||||||
|
if name then
|
||||||
|
return name, nil
|
||||||
|
end
|
||||||
|
return name
|
||||||
|
end
|
||||||
|
|
||||||
|
local out = {}
|
||||||
|
for name, etype in vim.fs.dir('testdir') do
|
||||||
|
out[name] = etype
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.uv.fs_scandir_next = orig
|
||||||
|
return out
|
||||||
|
]])
|
||||||
|
|
||||||
|
eq('file', result['test.txt'])
|
||||||
|
end)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('find()', function()
|
describe('find()', function()
|
||||||
|
|||||||
Reference in New Issue
Block a user