fs_scandir_next() sometimes returns name but no type
troiganto opened this issue · 6 comments
Hi!
After debugging for a long time why the LuaSnip plugin for Neovim doesn't load all my snippets, I think I've traced the issue to this (otherwise excellent!) library.
The documentation for luv.fs_scandir_next()
states that the function either returns two strings (file name and directory entry type) or nil
or it fails completely.
However, for whatever reason, on the specific machine that I use, I receive the file name as usual but nil
for the entry type. (MWE at the bottom.) Because downstream Lua scripts expect either two strings or two nils, this breaks a lot of their assumptions and code starts to behave strangely.
After some digging, I think I located the issue in fs.c:126: whenever ent->type
is an unknown enum value, the type is set to the string "unknown"
and two values are returned as usual. However, if ent->type
is the known enum value UV_DIRENT_UNKNOWN
, then the type is not set at all and only one value is returned.
I'm not sure what the correct behavior is here, but I think either the code should be changed (by merging the UV_DIRENT_UNKNOWN
with the default) or the docs (to inform users that sometimes string, nil
is returned).
Of course, if you can think of a simple reason why the directory entry type suddenly fails to be recognized, I'd be extremely grateful for any pointers. 🙂
No matter how you decide, thanks for your time and hard work!
Minimal example (though I'm not sure how useful it is on most machines):
$ git clone https://github.com/luvit/luv.git --recursive
...
$ cd luv
$ git describe
1.40.0-0-148-gd15a473
$ make
...
$ cd build
$ ./luajit ~/mwe.lua
where mwe.lua:
function Main()
local luv = require "luv"
local root = "<HOME>/.local/share/nvim/lazy/vim-snippets/snippets"
local fs = luv.fs_scandir(root)
local name, type = "", ""
while name do
name, type = luv.fs_scandir_next(fs)
print(type, name)
end
end
Main()
and the output is:
file _.snippets
file actionscript.snippets
file ada.snippets
file all.snippets
file alpaca.snippets
file apache.snippets
file arduino.snippets
file asm.snippets
file autoit.snippets
file awk.snippets
file bash.snippets
file c.snippets
file chef.snippets
file clojure.snippets
file cmake.snippets
file codeigniter.snippets
directory coffee
file cpp.snippets
nil crystal.snippets
nil cs.snippets
nil css.snippets
nil cuda.snippets
nil d.snippets
nil dart-flutter.snippets
nil dart.snippets
nil diff.snippets
nil django.snippets
nil dosini.snippets
nil eelixir.snippets
nil elixir.snippets
nil elm.snippets
nil erlang.snippets
nil eruby.snippets
nil falcon.snippets
nil fortran.snippets
nil freemarker.snippets
nil fsharp.snippets
nil gdscript.snippets
nil gitcommit.snippets
nil gleam.snippets
nil go.snippets
nil haml.snippets
nil handlebars.snippets
nil haskell.snippets
nil heex.snippets
nil helm.snippets
nil html.snippets
nil htmldjango.snippets
nil htmltornado.snippets
nil idris.snippets
nil jade.snippets
nil java.snippets
nil javascript
nil javascript-bemjson.snippets
nil javascript-d3.snippets
nil javascript-jasmine.snippets
nil javascript-mocha.snippets
nil javascript-openui5.snippets
nil jenkins.snippets
nil jinja.snippets
nil jsp.snippets
nil julia.snippets
nil kotlin.snippets
nil laravel.snippets
nil ledger.snippets
nil lfe.snippets
nil liquid.snippets
nil lpc.snippets
nil ls.snippets
nil lua.snippets
nil make.snippets
nil mako.snippets
nil markdown.snippets
nil matlab.snippets
nil mustache.snippets
nil objc.snippets
nil ocaml.snippets
nil octave.snippets
nil openfoam.snippets
nil org.snippets
nil pandoc.snippets
nil perl.snippets
nil perl6.snippets
nil phoenix.snippets
nil php.snippets
nil plsql.snippets
nil po.snippets
nil processing.snippets
nil progress.snippets
nil ps1.snippets
nil puppet.snippets
nil purescript.snippets
nil python.snippets
nil r.snippets
nil racket.snippets
nil rails.snippets
nil reason.snippets
nil rmd.snippets
nil rst.snippets
nil ruby.snippets
nil rust.snippets
nil sass.snippets
nil scala.snippets
nil scheme.snippets
nil scss.snippets
nil sh.snippets
nil simplemvcf.snippets
nil slim.snippets
nil smarty.snippets
nil snippets.snippets
nil sql.snippets
nil stylus.snippets
nil supercollider.snippets
nil svelte.snippets
nil systemverilog.snippets
nil tcl.snippets
nil tex.snippets
nil textile.snippets
nil twig.snippets
nil typescript.snippets
nil typescriptreact.snippets
nil verilog.snippets
nil vhdl.snippets
nil vim.snippets
nil vue.snippets
nil xml.snippets
nil xslt.snippets
nil yii-chtml.snippets
nil yii.snippets
nil zsh.snippets
nil nil
Odd that it works then stops working.
According to https://www.gnu.org/software/libc/manual/html_node/Directory-Entries.html:
The type is unknown. Only some filesystems have full support to return the type of the file, others might always return this value.
On what OS and filesystem do you see this happening?
It's odd indeed, it took me a long time to piece together what was happening here.
I access the affected machine remotely, it's managed by our IT department. I'm in contact with them in parallel to figure out if there's something they can do.
$ cat /etc/redhat-release
Rocky Linux release 9.2 (Blue Onyx)
$ mount | grep "/home "
<...> on /home type nfs4 (rw,nosuid,nodev,relatime,vers=4.2,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=<ip addr>,local_lock=none,addr=<ip addr>)
I wonder if the network filesystem and its inevitable lag cause any shenanigans somewhere inside libuv …
Surprised that our docs don't mention this; that should definitely be fixed regardless of whether we return nil
or "unknown"
as the type.
I assume the same applies to fs_readdir
but the Libuv docs don't mention it there. Will need to try to test that as well.
We can make this return a "unknown" string as well, this is probably the more correct behavior here. I remember reading the code relating this, should be an easy fix, assuming it wasn't intended behavior this shouldn't be a breaking change as well.
Personally, I think I prefer keeping it as nil
, or at least keeping some way of distinguishing between 'we got a type but it's unknown--it is not one of the known types though' and 'we didn't get a type at all--it could be anything, you probably need to do a stat call to find out for sure'.
I can confirm that fs_readdir()
shows the same behavior:
function Main()
local luv = require "luv"
local root = "<home>/.local/share/nvim/lazy/vim-snippets/snippets"
local fs = luv.fs_opendir(root, nil, 200)
local data = luv.fs_readdir(fs)
if data then
for _, dirent in ipairs(data) do
print(dirent.type, dirent.name)
end
end
if luv.fs_readdir(fs) then
print("...")
end
luv.fs_closedir(fs)
end
Main()
This prints the same (lengthy) output as the original example.
Explicitly using stat
on the entries that couldn't be read seems to kick the filesystem into doing its job. The following script correctly marks all files as "file"
instead of nil
:
function Main()
local luv = require "luv"
local root = "<home>/.local/share/nvim/lazy/vim-snippets/snippets"
local fs = luv.fs_scandir(root)
local name, type = "", ""
while name do
name, type = luv.fs_scandir_next(fs)
if name and not type then
local stat = luv.fs_stat(root .. "/" .. name)
type = stat.type
end
print(type, name)
end
end
Main()