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 }