You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
154 lines
4.1 KiB
154 lines
4.1 KiB
// +build linux |
|
|
|
package syscallcompat |
|
|
|
// Other implementations of getdents in Go: |
|
// https://github.com/ericlagergren/go-gnulib/blob/cb7a6e136427e242099b2c29d661016c19458801/dirent/getdents_unix.go |
|
// https://github.com/golang/tools/blob/5831d16d18029819d39f99bdc2060b8eff410b6b/imports/fastwalk_unix.go |
|
|
|
import ( |
|
"bytes" |
|
"sync" |
|
"syscall" |
|
"unsafe" |
|
|
|
"golang.org/x/sys/unix" |
|
) |
|
|
|
const sizeofDirent = int(unsafe.Sizeof(unix.Dirent{})) |
|
|
|
// maxReclen sanity check: Reclen should never be larger than this. |
|
// Due to padding between entries, it is 280 even on 32-bit architectures. |
|
// See https://github.com/rfjakob/gocryptfs/issues/197 for details. |
|
const maxReclen = 280 |
|
|
|
type DirEntry struct { |
|
Name string |
|
Mode uint32 |
|
} |
|
|
|
// getdents wraps unix.Getdents and converts the result to []fuse.DirEntry. |
|
func getdents(fd int) (entries []DirEntry, entriesSpecial []DirEntry, err error) { |
|
// Collect syscall result in smartBuf. |
|
// "bytes.Buffer" is smart about expanding the capacity and avoids the |
|
// exponential runtime of simple append(). |
|
var smartBuf bytes.Buffer |
|
tmp := make([]byte, 10000) |
|
for { |
|
n, err := unix.Getdents(fd, tmp) |
|
// unix.Getdents has been observed to return EINTR on cifs mounts |
|
if err == unix.EINTR { |
|
if n > 0 { |
|
smartBuf.Write(tmp[:n]) |
|
} |
|
continue |
|
} else if err != nil { |
|
if smartBuf.Len() > 0 { |
|
return nil, nil, syscall.EIO |
|
} |
|
return nil, nil, err |
|
} |
|
if n == 0 { |
|
break |
|
} |
|
smartBuf.Write(tmp[:n]) |
|
} |
|
// Make sure we have at least Sizeof(Dirent) of zeros after the last |
|
// entry. This prevents a cast to Dirent from reading past the buffer. |
|
smartBuf.Grow(sizeofDirent) |
|
buf := smartBuf.Bytes() |
|
// Count the number of directory entries in the buffer so we can allocate |
|
// a fuse.DirEntry slice of the correct size at once. |
|
var numEntries, offset int |
|
for offset < len(buf) { |
|
s := *(*unix.Dirent)(unsafe.Pointer(&buf[offset])) |
|
if s.Reclen == 0 { |
|
// EBADR = Invalid request descriptor |
|
return nil, nil, syscall.EBADR |
|
} |
|
if int(s.Reclen) > maxReclen { |
|
return nil, nil, syscall.EBADR |
|
} |
|
offset += int(s.Reclen) |
|
numEntries++ |
|
} |
|
// Parse the buffer into entries. |
|
// Note: syscall.ParseDirent() only returns the names, |
|
// we want all the data, so we have to implement |
|
// it on our own. |
|
entries = make([]DirEntry, 0, numEntries) |
|
offset = 0 |
|
for offset < len(buf) { |
|
s := *(*unix.Dirent)(unsafe.Pointer(&buf[offset])) |
|
name, err := getdentsName(s) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
offset += int(s.Reclen) |
|
if name == "." || name == ".." { |
|
// These are always directories, no need to call convertDType. |
|
entriesSpecial = append(entriesSpecial, DirEntry{ |
|
Mode: syscall.S_IFDIR, |
|
Name: name, |
|
}) |
|
continue |
|
} |
|
mode, err := convertDType(fd, name, s.Type) |
|
if err != nil { |
|
// The file may have been deleted in the meantime. Just skip it |
|
// and go on. |
|
continue |
|
} |
|
entries = append(entries, DirEntry{ |
|
Mode: mode, |
|
Name: name, |
|
}) |
|
} |
|
return entries, entriesSpecial, nil |
|
} |
|
|
|
// getdentsName extracts the filename from a Dirent struct and returns it as |
|
// a Go string. |
|
func getdentsName(s unix.Dirent) (string, error) { |
|
// After the loop, l contains the index of the first '\0'. |
|
l := 0 |
|
for l = range s.Name { |
|
if s.Name[l] == 0 { |
|
break |
|
} |
|
} |
|
if l < 1 { |
|
// EBADR = Invalid request descriptor |
|
return "", syscall.EBADR |
|
} |
|
// Copy to byte slice. |
|
name := make([]byte, l) |
|
for i := range name { |
|
name[i] = byte(s.Name[i]) |
|
} |
|
return string(name), nil |
|
} |
|
|
|
var dtUnknownWarnOnce sync.Once |
|
|
|
func dtUnknownWarn(dirfd int) { |
|
var buf syscall.Statfs_t |
|
syscall.Fstatfs(dirfd, &buf) |
|
} |
|
|
|
// convertDType converts a Dirent.Type to at Stat_t.Mode value. |
|
func convertDType(dirfd int, name string, dtype uint8) (uint32, error) { |
|
if dtype != syscall.DT_UNKNOWN { |
|
// Shift up by four octal digits = 12 bits |
|
return uint32(dtype) << 12, nil |
|
} |
|
// DT_UNKNOWN: we have to call stat() |
|
dtUnknownWarnOnce.Do(func() { dtUnknownWarn(dirfd) }) |
|
var st unix.Stat_t |
|
err := Fstatat(dirfd, name, &st, unix.AT_SYMLINK_NOFOLLOW) |
|
if err != nil { |
|
return 0, err |
|
} |
|
// The S_IFMT bit mask extracts the file type from the mode. |
|
return st.Mode & syscall.S_IFMT, nil |
|
}
|
|
|