diff --git a/backend/local/metadata_linux.go b/backend/local/metadata_linux.go index 17d32c06b..b274148e7 100644 --- a/backend/local/metadata_linux.go +++ b/backend/local/metadata_linux.go @@ -5,19 +5,41 @@ package local import ( "fmt" + "sync" "time" "github.com/rclone/rclone/fs" "golang.org/x/sys/unix" ) +var ( + statxCheckOnce sync.Once + readMetadataFromFileFn func(o *Object, m *fs.Metadata) (err error) +) + // Read the metadata from the file into metadata where possible func (o *Object) readMetadataFromFile(m *fs.Metadata) (err error) { + statxCheckOnce.Do(func() { + // Check statx() is available as it was only introduced in kernel 4.11 + // If not, fall back to fstatat() which was introduced in 2.6.16 which is guaranteed for all Go versions + var stat unix.Statx_t + if unix.Statx(unix.AT_FDCWD, ".", 0, unix.STATX_ALL, &stat) != unix.ENOSYS { + readMetadataFromFileFn = readMetadataFromFileStatx + } else { + readMetadataFromFileFn = readMetadataFromFileFstatat + } + }) + return readMetadataFromFileFn(o, m) +} + +// Read the metadata from the file into metadata where possible +func readMetadataFromFileStatx(o *Object, m *fs.Metadata) (err error) { flags := unix.AT_SYMLINK_NOFOLLOW if o.fs.opt.FollowSymlinks { flags = 0 } var stat unix.Statx_t + // statx() was added to Linux in kernel 4.11 err = unix.Statx(unix.AT_FDCWD, o.path, flags, (0 | unix.STATX_TYPE | // Want stx_mode & S_IFMT unix.STATX_MODE | // Want stx_mode & ~S_IFMT @@ -45,3 +67,36 @@ func (o *Object) readMetadataFromFile(m *fs.Metadata) (err error) { setTime("btime", stat.Btime) return nil } + +// Read the metadata from the file into metadata where possible +func readMetadataFromFileFstatat(o *Object, m *fs.Metadata) (err error) { + flags := unix.AT_SYMLINK_NOFOLLOW + if o.fs.opt.FollowSymlinks { + flags = 0 + } + var stat unix.Stat_t + // fstatat() was added to Linux in kernel 2.6.16 + // Go only supports 2.6.32 or later + err = unix.Fstatat(unix.AT_FDCWD, o.path, &stat, flags) + if err != nil { + return err + } + m.Set("mode", fmt.Sprintf("%0o", stat.Mode)) + m.Set("uid", fmt.Sprintf("%d", stat.Uid)) + m.Set("gid", fmt.Sprintf("%d", stat.Gid)) + if stat.Rdev != 0 { + m.Set("rdev", fmt.Sprintf("%x", stat.Rdev)) + } + setTime := func(key string, t unix.Timespec) { + // The types of t.Sec and t.Nsec vary from int32 to int64 on + // different Linux architectures so we need to cast them to + // int64 here and hence need to quiet the linter about + // unecessary casts. + // + // nolint: unconvert + m.Set(key, time.Unix(int64(t.Sec), int64(t.Nsec)).Format(metadataTimeFormat)) + } + setTime("atime", stat.Atim) + setTime("mtime", stat.Mtim) + return nil +}