2022-08-28 11:11:36 +02:00
//go:build linux
2017-08-13 21:13:44 +02:00
// +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"
2017-09-03 15:05:54 +02:00
"sync"
2017-08-13 21:13:44 +02:00
"syscall"
"unsafe"
2017-12-03 17:57:08 +01:00
"golang.org/x/sys/unix"
2020-05-17 14:18:23 +02:00
"github.com/hanwen/go-fuse/v2/fuse"
2017-08-13 21:13:44 +02:00
2021-08-23 15:05:15 +02:00
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
2017-08-13 21:13:44 +02:00
)
2018-01-31 18:59:10 +01:00
const sizeofDirent = int ( unsafe . Sizeof ( unix . Dirent { } ) )
2017-08-13 21:13:44 +02:00
2018-01-25 22:22:13 +01:00
// maxReclen sanity check: Reclen should never be larger than this.
// Due to padding between entries, it is 280 even on 32-bit architectures.
2021-08-30 11:31:01 +02:00
// See https://github.com/rfjakob/gocryptfs/issues/197 for details.
2018-01-25 22:22:13 +01:00
const maxReclen = 280
2018-01-31 18:59:10 +01:00
// getdents wraps unix.Getdents and converts the result to []fuse.DirEntry.
2021-05-23 12:00:09 +02:00
func getdents ( fd int ) ( entries [ ] fuse . DirEntry , entriesSpecial [ ] fuse . DirEntry , err error ) {
2017-08-13 21:13:44 +02:00
// 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 {
2018-01-31 18:59:10 +01:00
n , err := unix . Getdents ( fd , tmp )
2020-05-23 22:54:23 +02:00
// 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 {
2020-05-24 23:30:25 +02:00
if smartBuf . Len ( ) > 0 {
2021-08-30 11:31:01 +02:00
tlog . Warn . Printf ( "warning: unix.Getdents returned errno %d in the middle of data ( https://github.com/rfjakob/gocryptfs/issues/483 )" , err . ( syscall . Errno ) )
2021-05-23 12:00:09 +02:00
return nil , nil , syscall . EIO
2020-05-24 23:30:25 +02:00
}
2021-05-23 12:00:09 +02:00
return nil , nil , err
2017-08-13 21:13:44 +02:00
}
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 ) {
2018-01-31 18:59:10 +01:00
s := * ( * unix . Dirent ) ( unsafe . Pointer ( & buf [ offset ] ) )
2017-08-13 21:13:44 +02:00
if s . Reclen == 0 {
tlog . Warn . Printf ( "Getdents: corrupt entry #%d: Reclen=0 at offset=%d. Returning EBADR" ,
numEntries , offset )
// EBADR = Invalid request descriptor
2021-05-23 12:00:09 +02:00
return nil , nil , syscall . EBADR
2017-08-13 21:13:44 +02:00
}
2018-01-25 22:22:13 +01:00
if int ( s . Reclen ) > maxReclen {
2017-08-13 21:13:44 +02:00
tlog . Warn . Printf ( "Getdents: corrupt entry #%d: Reclen=%d > %d. Returning EBADR" ,
2018-01-25 22:22:13 +01:00
numEntries , s . Reclen , maxReclen )
2021-05-23 12:00:09 +02:00
return nil , nil , syscall . EBADR
2017-08-13 21:13:44 +02:00
}
offset += int ( s . Reclen )
numEntries ++
}
2018-01-25 08:43:30 +01:00
// 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.
2021-05-23 12:00:09 +02:00
entries = make ( [ ] fuse . DirEntry , 0 , numEntries )
2017-08-13 21:13:44 +02:00
offset = 0
for offset < len ( buf ) {
2018-01-31 18:59:10 +01:00
s := * ( * unix . Dirent ) ( unsafe . Pointer ( & buf [ offset ] ) )
2017-08-13 21:13:44 +02:00
name , err := getdentsName ( s )
if err != nil {
2021-05-23 12:00:09 +02:00
return nil , nil , err
2017-08-13 21:13:44 +02:00
}
offset += int ( s . Reclen )
if name == "." || name == ".." {
2021-05-23 12:00:09 +02:00
// These are always directories, no need to call convertDType.
entriesSpecial = append ( entriesSpecial , fuse . DirEntry {
Ino : s . Ino ,
Mode : syscall . S_IFDIR ,
Name : name ,
} )
2017-08-13 21:13:44 +02:00
continue
}
2017-12-03 17:57:08 +01:00
mode , err := convertDType ( fd , name , s . Type )
2017-08-13 21:13:44 +02:00
if err != nil {
// The file may have been deleted in the meantime. Just skip it
// and go on.
continue
}
entries = append ( entries , fuse . DirEntry {
Ino : s . Ino ,
Mode : mode ,
Name : name ,
} )
}
2021-05-23 12:00:09 +02:00
return entries , entriesSpecial , nil
2017-08-13 21:13:44 +02:00
}
// getdentsName extracts the filename from a Dirent struct and returns it as
// a Go string.
2018-01-31 18:59:10 +01:00
func getdentsName ( s unix . Dirent ) ( string , error ) {
2017-08-13 21:13:44 +02:00
// 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 {
tlog . Warn . Printf ( "Getdents: invalid name length l=%d. Returning EBADR" , l )
// 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
}
2017-09-03 15:05:54 +02:00
var dtUnknownWarnOnce sync . Once
2018-11-17 17:44:21 +01:00
func dtUnknownWarn ( dirfd int ) {
const XFS_SUPER_MAGIC = 0x58465342 // From man 2 statfs
var buf syscall . Statfs_t
err := syscall . Fstatfs ( dirfd , & buf )
if err == nil && buf . Type == XFS_SUPER_MAGIC {
// Old XFS filesystems always return DT_UNKNOWN. Downgrade the message
// to "info" level if we are on XFS.
2021-08-30 11:31:01 +02:00
// https://github.com/rfjakob/gocryptfs/issues/267
2018-11-17 17:44:21 +01:00
tlog . Info . Printf ( "Getdents: convertDType: received DT_UNKNOWN, fstype=xfs, falling back to stat" )
} else {
tlog . Warn . Printf ( "Getdents: convertDType: received DT_UNKNOWN, fstype=%#x, falling back to stat" ,
buf . Type )
}
}
2017-08-13 21:13:44 +02:00
// convertDType converts a Dirent.Type to at Stat_t.Mode value.
2017-12-03 17:57:08 +01:00
func convertDType ( dirfd int , name string , dtype uint8 ) ( uint32 , error ) {
2017-08-13 21:13:44 +02:00
if dtype != syscall . DT_UNKNOWN {
// Shift up by four octal digits = 12 bits
return uint32 ( dtype ) << 12 , nil
}
2017-12-03 17:57:08 +01:00
// DT_UNKNOWN: we have to call stat()
2018-11-17 17:44:21 +01:00
dtUnknownWarnOnce . Do ( func ( ) { dtUnknownWarn ( dirfd ) } )
2017-12-03 17:57:08 +01:00
var st unix . Stat_t
err := Fstatat ( dirfd , name , & st , unix . AT_SYMLINK_NOFOLLOW )
2017-08-13 21:13:44 +02:00
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
}