ReadDir is not setting FileInfo up correctly for symlinks
TLDR: When one calls IsDir() on a FileInfo returned back from afero.ReadDir, and that FileInfo is for a symlink to another directory, the IsDir() function returns false. When one gets a FileInfo from os.Stat for the same symlink, IsDir() returns true.
Example Dir:
$ ll /tmp/foo
total 8
drwxr-xr-x 5 anand wheel 160B Feb 2 16:48 .
drwxrwxrwt 13 root wheel 416B Feb 2 17:28 ..
drwxr-xr-x 2 anand wheel 64B Feb 2 16:47 bar
lrwxr-xr-x 1 anand wheel 3B Feb 2 16:47 baz -> bar
-rw-r--r-- 1 anand wheel 731B Feb 2 17:05 main.go
Example code (from main.go):
package main
import (
"fmt"
"os"
"github.com/spf13/afero"
)
func main() {
fmt.Println("os: bar is a dir:", isDir("bar"))
fmt.Println("os: baz is a dir:", isDir("baz"))
fs := afero.NewOsFs()
fmt.Println("afero: bar is a dir:", isDirAfero(fs, "bar"))
fmt.Println("afero: baz is a dir:", isDirAfero(fs, "baz"))
infos, err := afero.ReadDir(fs, "/tmp/foo")
if err != nil {
panic(err)
}
for _, info := range infos {
fmt.Println("afero.ReadDir", info.Name(), "is a dir:", info.IsDir())
}
}
func isDir(p string) bool {
i, err := os.Stat(p)
if err != nil {
panic(err)
}
return i.IsDir()
}
func isDirAfero(fs afero.Fs, p string) bool {
i, err := fs.Stat(p)
if err != nil {
panic(err)
}
return i.IsDir()
}
Output of main.go:
$ cd /tmp/foo
$ go run main.go
os: bar is a dir: true
os: baz is a dir: true
afero: bar is a dir: true
afero: baz is a dir: true
afero.ReadDir bar is a dir: true
afero.ReadDir baz is a dir: false
afero.ReadDir main.go is a dir: false
As we can see the following line from the output was unexpected:
afero.ReadDir baz is a dir: false
What I expected
I expected Info.IsDir() to return true for baz.
This doesn't seem to be a library problem, this is due to the functioning of de 'os'.
infos, _ := os.ReadDir("/tmp/foo")
for _, info := range infos {
fmt.Println("", info.Name(), "is a dir:", info.IsDir())
}
the output is:
bar is a dir: true
baz is a dir: false
The error seems to be caused by using a different function to fill in the stat data: os.ReadDir, will call Lstat and not Stat function, in their subdirectories.
go/src/os/dir_unix.go
You can check this by looking for the function func (f *File) readdir
and see that the stats are filled by calling
info, err := lstat(f.name + "/" + string(name))
This has an impact on afero.Walk as it relies on IsDir(). If you only care about following the symlink of the root directory and you're using OsFs, then using EvalSymlinks is a viable workaround. This, however, does not help my case as I must support different filesystems.