From 9bf78d0373ba33a15f61c1bb2a3fc7c62639aec0 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Wed, 5 Oct 2022 10:56:01 +0100 Subject: [PATCH] local: fix "Failed to read metadata: function not implemented" on old Linux kernels Before this change rclone used statx() to read the metadata for files from the local filesystem when `-M` was in use. Unfortunately statx() was only introduced in kernel 4.11 which was released in April 2017 so there are current systems (eg Centos 7) still on kernel versions which don't support statx(). This patch checks to see if statx() is available and if it isn't, it falls back to using fstatat() which was introduced in Linux 2.6.16 which is guaranteed for all Go versions. See: https://forum.rclone.org/t/metadata-from-linux-local-s3-failed-to-copy-failed-to-read-metadata-from-source-object-function-not-implemented/33233/ --- backend/local/metadata_linux.go | 55 +++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) 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 +}