package lockfile import ( "errors" "fmt" "io/fs" "os" "syscall" ) type Lock struct { file *os.File } func Acquire(path, noun, holder string) (*Lock, error) { file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR|syscall.O_NOFOLLOW|syscall.O_CLOEXEC, 0o600) if err != nil { return nil, fmt.Errorf("open %s %s: %w", noun, path, err) } fi, err := file.Stat() if err != nil { file.Close() return nil, fmt.Errorf("inspect %s %s: %w", noun, path, err) } if !fi.Mode().IsRegular() { file.Close() return nil, fmt.Errorf("%s %s is a %s, not a regular file", noun, path, DescribeType(fi.Mode())) } for { err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) if err == nil { return &Lock{file: file}, nil } if err == syscall.EINTR { continue } file.Close() if err == syscall.EWOULDBLOCK || err == syscall.EAGAIN { return nil, fmt.Errorf("%s %s is held by another process; %s", noun, path, holder) } return nil, fmt.Errorf("acquire %s %s: %w", noun, path, err) } } func (l *Lock) Release() error { unlockErr := syscall.Flock(int(l.file.Fd()), syscall.LOCK_UN) return errors.Join(unlockErr, l.file.Close()) } func DescribeType(m fs.FileMode) string { switch { case m.IsRegular(): return "regular file" case m.IsDir(): return "directory" case m&fs.ModeSymlink != 0: return "symbolic link" case m&fs.ModeSocket != 0: return "socket" case m&fs.ModeNamedPipe != 0: return "named pipe" case m&fs.ModeDevice != 0: return "device" } return "file of type " + m.Type().String() }