//go:build linux // The flock package provides a file-system mediated locking mechanism on linux // using the `flock` system call. package flock import ( "errors" "os" "syscall" ) var ErrLocked = errors.New("locked") // Lock gets an exclusive lock on the file at the given path. If the file // doesn't exist, it's created. func Lock(path string) (*os.File, error) { return lock(path, syscall.LOCK_EX) } // TryLock attempts to lock the file at path. It will return ErrLocked if the // file is already locked. func TryLock(path string) (*os.File, error) { return lock(path, syscall.LOCK_EX|syscall.LOCK_NB) } // LockFile is like Lock, but takes an *os.File. func LockFile(f *os.File) error { return lockFile(f, syscall.LOCK_EX) } // TryLockFile is like TryLock, but takes an *os.File. func TryLockFile(f *os.File) error { return lockFile(f, syscall.LOCK_EX|syscall.LOCK_NB) } func lockFile(f *os.File, flags int) error { if err := flock(int(f.Fd()), flags); err != nil { if flags&syscall.LOCK_NB != 0 && errors.Is(err, syscall.EAGAIN) { return ErrLocked } return err } return nil } func flock(fd int, how int) error { _, _, e1 := syscall.Syscall(syscall.SYS_FLOCK, uintptr(fd), uintptr(how), 0) if e1 != 0 { return syscall.Errno(e1) } return nil } func lock(path string, flags int) (*os.File, error) { perm := os.O_CREATE | os.O_RDWR f, err := os.OpenFile(path, perm, 0600) if err != nil { return nil, err } err = lockFile(f, flags) if err != nil { f.Close() f = nil } return f, err } // Unlock releases the lock acquired via the Lock function. func Unlock(f *os.File) error { if err := flock(int(f.Fd()), syscall.LOCK_UN); err != nil { return err } return f.Close() }