github.com/telepresenceio/telepresence/v2@v2.20.0-pro.6.0.20240517030216-236ea954e789/pkg/client/logging/stat_linux.go (about) 1 package logging 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "time" 8 9 "golang.org/x/sys/unix" 10 11 "github.com/telepresenceio/telepresence/v2/pkg/dos" 12 ) 13 14 type statable interface { 15 Fd() uintptr 16 Name() string 17 } 18 19 type fileInfo struct { 20 size int64 21 uid int 22 gid int 23 btime time.Time 24 mtime time.Time 25 ctime time.Time 26 } 27 28 func osFStat(dfile dos.File) (SysInfo, error) { 29 file, ok := dfile.(statable) 30 if !ok { 31 return nil, fmt.Errorf("files of type %T don't support Fstat", dfile) 32 } 33 const want = 0 | 34 unix.STATX_SIZE | 35 unix.STATX_UID | 36 unix.STATX_GID | 37 unix.STATX_BTIME | 38 unix.STATX_MTIME | 39 unix.STATX_CTIME 40 41 var stat unix.Statx_t 42 if err := unix.Statx(int(file.Fd()), "", unix.AT_EMPTY_PATH, want, &stat); err != nil { 43 if errors.Is(err, unix.ENOSYS) { 44 // The statx(2) system call was introduced in Linux 4.11 (2017). That's new 45 // enough that we should have a fallback. 46 return oldFStat(file) 47 } 48 return nil, fmt.Errorf("failed to statx %s: %w", file.Name(), err) 49 } 50 51 if stat.Mask&want != want { 52 // Not all filesystems (notably: tmpfs) support btime. 53 return oldFStat(file) 54 } 55 56 return fileInfo{ 57 size: int64(stat.Size), 58 uid: int(stat.Uid), 59 gid: int(stat.Gid), 60 btime: time.Unix(stat.Btime.Sec, int64(stat.Btime.Nsec)), 61 mtime: time.Unix(stat.Mtime.Sec, int64(stat.Mtime.Nsec)), 62 ctime: time.Unix(stat.Ctime.Sec, int64(stat.Ctime.Nsec)), 63 }, nil 64 } 65 66 func oldFStat(file statable) (SysInfo, error) { 67 var stat unix.Stat_t 68 if err := unix.Fstat(int(file.Fd()), &stat); err != nil { 69 return nil, fmt.Errorf("failed to stat %s: %w", file.Name(), err) 70 } 71 return fileInfo{ 72 size: stat.Size, 73 uid: int(stat.Uid), 74 gid: int(stat.Gid), 75 // The reason we wanted statx(2) in the first place is 76 // because fstat(2) doesn't give us the birthtime. Fake it 77 // with the changetime. I'm not sure why changetime is the 78 // best choice, but it's what Telepresence did before we 79 // added statx support. 80 btime: time.Unix(stat.Ctim.Sec, stat.Ctim.Nsec), 81 mtime: time.Unix(stat.Mtim.Sec, stat.Mtim.Nsec), 82 ctime: time.Unix(stat.Ctim.Sec, stat.Ctim.Nsec), 83 }, nil 84 } 85 86 func (u fileInfo) Size() int64 { 87 return u.size 88 } 89 90 func (u fileInfo) SetOwnerAndGroup(name string) error { 91 return os.Chown(name, u.uid, u.gid) 92 } 93 94 func (u fileInfo) HaveSameOwnerAndGroup(other SysInfo) bool { 95 ou := other.(fileInfo) 96 return u.uid == ou.uid && u.gid == ou.gid 97 } 98 99 func (u fileInfo) String() string { 100 return fmt.Sprintf("BTIME %v, MTIME %v, CTIME %v, UID %d, GID %d", 101 u.btime, u.mtime, u.ctime, u.uid, u.gid) 102 } 103 104 func (u fileInfo) BirthTime() time.Time { return u.btime } 105 func (u fileInfo) ModifyTime() time.Time { return u.mtime } 106 func (u fileInfo) ChangeTime() time.Time { return u.ctime }