github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/backend/local/metadata_linux.go (about)

     1  //go:build linux
     2  
     3  package local
     4  
     5  import (
     6  	"fmt"
     7  	"os"
     8  	"runtime"
     9  	"sync"
    10  	"syscall"
    11  	"time"
    12  
    13  	"github.com/rclone/rclone/fs"
    14  	"golang.org/x/sys/unix"
    15  )
    16  
    17  var (
    18  	statxCheckOnce         sync.Once
    19  	readMetadataFromFileFn func(o *Object, m *fs.Metadata) (err error)
    20  )
    21  
    22  // Read the time specified from the os.FileInfo
    23  func readTime(t timeType, fi os.FileInfo) time.Time {
    24  	stat, ok := fi.Sys().(*syscall.Stat_t)
    25  	if !ok {
    26  		fs.Debugf(nil, "didn't return Stat_t as expected")
    27  		return fi.ModTime()
    28  	}
    29  	switch t {
    30  	case aTime:
    31  		return time.Unix(stat.Atim.Unix())
    32  	case cTime:
    33  		return time.Unix(stat.Ctim.Unix())
    34  	}
    35  	return fi.ModTime()
    36  }
    37  
    38  // Read the metadata from the file into metadata where possible
    39  func (o *Object) readMetadataFromFile(m *fs.Metadata) (err error) {
    40  	statxCheckOnce.Do(func() {
    41  		// Check statx() is available as it was only introduced in kernel 4.11
    42  		// If not, fall back to fstatat() which was introduced in 2.6.16 which is guaranteed for all Go versions
    43  		var stat unix.Statx_t
    44  		if runtime.GOOS != "android" && unix.Statx(unix.AT_FDCWD, ".", 0, unix.STATX_ALL, &stat) != unix.ENOSYS {
    45  			readMetadataFromFileFn = readMetadataFromFileStatx
    46  		} else {
    47  			readMetadataFromFileFn = readMetadataFromFileFstatat
    48  		}
    49  	})
    50  	return readMetadataFromFileFn(o, m)
    51  }
    52  
    53  // Read the metadata from the file into metadata where possible
    54  func readMetadataFromFileStatx(o *Object, m *fs.Metadata) (err error) {
    55  	flags := unix.AT_SYMLINK_NOFOLLOW
    56  	if o.fs.opt.FollowSymlinks {
    57  		flags = 0
    58  	}
    59  	var stat unix.Statx_t
    60  	//  statx() was added to Linux in kernel 4.11
    61  	err = unix.Statx(unix.AT_FDCWD, o.path, flags, (0 |
    62  		unix.STATX_TYPE | // Want stx_mode & S_IFMT
    63  		unix.STATX_MODE | // Want stx_mode & ~S_IFMT
    64  		unix.STATX_UID | // Want stx_uid
    65  		unix.STATX_GID | // Want stx_gid
    66  		unix.STATX_ATIME | // Want stx_atime
    67  		unix.STATX_MTIME | // Want stx_mtime
    68  		unix.STATX_CTIME | // Want stx_ctime
    69  		unix.STATX_BTIME), // Want stx_btime
    70  		&stat)
    71  	if err != nil {
    72  		return err
    73  	}
    74  	m.Set("mode", fmt.Sprintf("%0o", stat.Mode))
    75  	m.Set("uid", fmt.Sprintf("%d", stat.Uid))
    76  	m.Set("gid", fmt.Sprintf("%d", stat.Gid))
    77  	if stat.Rdev_major != 0 || stat.Rdev_minor != 0 {
    78  		m.Set("rdev", fmt.Sprintf("%x", uint64(stat.Rdev_major)<<32|uint64(stat.Rdev_minor)))
    79  	}
    80  	setTime := func(key string, t unix.StatxTimestamp) {
    81  		m.Set(key, time.Unix(t.Sec, int64(t.Nsec)).Format(metadataTimeFormat))
    82  	}
    83  	setTime("atime", stat.Atime)
    84  	setTime("mtime", stat.Mtime)
    85  	setTime("btime", stat.Btime)
    86  	return nil
    87  }
    88  
    89  // Read the metadata from the file into metadata where possible
    90  func readMetadataFromFileFstatat(o *Object, m *fs.Metadata) (err error) {
    91  	flags := unix.AT_SYMLINK_NOFOLLOW
    92  	if o.fs.opt.FollowSymlinks {
    93  		flags = 0
    94  	}
    95  	var stat unix.Stat_t
    96  	// fstatat() was added to Linux in  kernel  2.6.16
    97  	// Go only supports 2.6.32 or later
    98  	err = unix.Fstatat(unix.AT_FDCWD, o.path, &stat, flags)
    99  	if err != nil {
   100  		return err
   101  	}
   102  	m.Set("mode", fmt.Sprintf("%0o", stat.Mode))
   103  	m.Set("uid", fmt.Sprintf("%d", stat.Uid))
   104  	m.Set("gid", fmt.Sprintf("%d", stat.Gid))
   105  	if stat.Rdev != 0 {
   106  		m.Set("rdev", fmt.Sprintf("%x", stat.Rdev))
   107  	}
   108  	setTime := func(key string, t unix.Timespec) {
   109  		// The types of t.Sec and t.Nsec vary from int32 to int64 on
   110  		// different Linux architectures so we need to cast them to
   111  		// int64 here and hence need to quiet the linter about
   112  		// unnecessary casts.
   113  		//
   114  		// nolint: unconvert
   115  		m.Set(key, time.Unix(int64(t.Sec), int64(t.Nsec)).Format(metadataTimeFormat))
   116  	}
   117  	setTime("atime", stat.Atim)
   118  	setTime("mtime", stat.Mtim)
   119  	return nil
   120  }