//go:build linux // +build linux package staros import ( "golang.org/x/sys/unix" "os" "syscall" "time" ) type FileLock struct { fd int filepath string locked bool } func timespecToTime(ts syscall.Timespec) time.Time { return time.Unix(int64(ts.Sec), int64(ts.Nsec)) } func GetFileCreationTime(fileinfo os.FileInfo) time.Time { if fileinfo == nil { return time.Time{} } // Linux os.FileInfo/syscall.Stat_t does not expose a stable birth time. // Returning ctime here would be wrong because it tracks inode changes. return time.Time{} } func GetFileAccessTime(fileinfo os.FileInfo) time.Time { if fileinfo == nil { return time.Time{} } if stat, ok := fileinfo.Sys().(*syscall.Stat_t); ok && stat != nil { return timespecToTime(stat.Atim) } return time.Time{} } func SetFileTimes(file *os.File, info os.FileInfo) { _ = SetFileTimesE(file, info) } func SetFileTimesbyTime(file *os.File) { _ = SetFileTimesbyTimeE(file) } func SetFileTimesE(file *os.File, info os.FileInfo) error { if file == nil { return errNilFile } if info == nil { return errNilFileInfo } stat, ok := info.Sys().(*syscall.Stat_t) if !ok || stat == nil { return errUnsupportedFileInfo } atime := timespecToTime(stat.Atim) mtime := info.ModTime() return setFileTimes(file.Name(), atime, mtime) } func SetFileTimesByTimeE(file *os.File) error { return SetFileTimesbyTimeE(file) } func SetFileTimesbyTimeE(file *os.File) error { if file == nil { return errNilFile } now := time.Now() return setFileTimes(file.Name(), now, now) } func setFileTimes(path string, atime, mtime time.Time) error { ts := [2]unix.Timespec{ unix.NsecToTimespec(atime.UnixNano()), unix.NsecToTimespec(mtime.UnixNano()), } return unix.UtimesNanoAt(unix.AT_FDCWD, path, ts[:], unix.AT_SYMLINK_NOFOLLOW) } func (f *FileLock) openFileForLock() error { fd, err := syscall.Open(f.filepath, syscall.O_CREAT|syscall.O_RDONLY, 0600) if err != nil { return err } f.fd = fd return nil } func (f *FileLock) Lock(Exclusive bool) error { var lockType int if Exclusive { lockType = syscall.LOCK_EX } else { lockType = syscall.LOCK_SH } return f.lockWithFlags(lockType) } func (f *FileLock) LockNoBlocking(Exclusive bool) error { var lockType int if Exclusive { lockType = syscall.LOCK_EX } else { lockType = syscall.LOCK_SH } return f.lockWithFlags(lockType | syscall.LOCK_NB) } func (f *FileLock) lockWithFlags(lockType int) error { if f.locked { return ERR_ALREADY_LOCKED } if err := f.openFileForLock(); err != nil { return err } err := syscall.Flock(f.fd, lockType) if err != nil { _ = syscall.Close(f.fd) f.fd = 0 if err == syscall.EWOULDBLOCK { return ERR_ALREADY_LOCKED } return err } f.locked = true return nil } func (f *FileLock) Unlock() error { if f == nil || !f.locked { return errFileLockNotLocked } err := syscall.Flock(f.fd, syscall.LOCK_UN) if err != nil { return err } if err := syscall.Close(f.fd); err != nil { return err } f.fd = 0 f.locked = false return nil } func (f *FileLock) LockWithTimeout(tm time.Duration, Exclusive bool) error { if f.locked { return ERR_ALREADY_LOCKED } var lockType int if Exclusive { lockType = syscall.LOCK_EX } else { lockType = syscall.LOCK_SH } if tm < 0 { return f.Lock(Exclusive) } deadline := time.Now().Add(tm) for { err := f.lockWithFlags(lockType | syscall.LOCK_NB) if err == nil { return nil } if err != ERR_ALREADY_LOCKED { return err } if !time.Now().Before(deadline) { return ERR_TIMEOUT } sleep := time.Millisecond * 10 if remaining := time.Until(deadline); remaining < sleep { sleep = remaining } if sleep > 0 { time.Sleep(sleep) } } }