9bf78d0373
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/
102 lines
3.1 KiB
Go
102 lines
3.1 KiB
Go
//go:build linux
|
|
// +build linux
|
|
|
|
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
|
|
unix.STATX_UID | // Want stx_uid
|
|
unix.STATX_GID | // Want stx_gid
|
|
unix.STATX_ATIME | // Want stx_atime
|
|
unix.STATX_MTIME | // Want stx_mtime
|
|
unix.STATX_CTIME | // Want stx_ctime
|
|
unix.STATX_BTIME), // Want stx_btime
|
|
&stat)
|
|
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_major != 0 || stat.Rdev_minor != 0 {
|
|
m.Set("rdev", fmt.Sprintf("%x", uint64(stat.Rdev_major)<<32|uint64(stat.Rdev_minor)))
|
|
}
|
|
setTime := func(key string, t unix.StatxTimestamp) {
|
|
m.Set(key, time.Unix(t.Sec, int64(t.Nsec)).Format(metadataTimeFormat))
|
|
}
|
|
setTime("atime", stat.Atime)
|
|
setTime("mtime", stat.Mtime)
|
|
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
|
|
}
|