package agent import ( "errors" "os" "path/filepath" "golang.org/x/sys/unix" ) var ErrUploadBusy = errors.New("upload is already being handled by another process") type UploadLock struct{ file *os.File } // LockUpload uses an OS lock released on process death. The stable lock inode // is never removed, so independent processes cannot lock different inodes. func (s *Spool) LockUpload(id string) (*UploadLock, error) { if err := validateName(id); err != nil { return nil, err } root := filepath.Join(s.root, ".upload-locks") if err := os.MkdirAll(root, 0700); err != nil { return nil, err } file, err := os.OpenFile(filepath.Join(root, id), os.O_CREATE|os.O_RDWR, 0600) if err != nil { return nil, err } if err := unix.Flock(int(file.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil { closeErr := file.Close() if errors.Is(err, unix.EWOULDBLOCK) { return nil, errors.Join(ErrUploadBusy, closeErr) } return nil, errors.Join(err, closeErr) } return &UploadLock{file: file}, nil } func (l *UploadLock) Close() error { if l == nil || l.file == nil { return nil } unlockErr := unix.Flock(int(l.file.Fd()), unix.LOCK_UN) closeErr := l.file.Close() l.file = nil return errors.Join(unlockErr, closeErr) }