package file import ( "io/ioutil" "os" "path/filepath" "strconv" "syscall" "github.com/containers/storage" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) // FileLocks is a struct enabling POSIX lock locking in a shared memory // segment. type FileLocks struct { // nolint lockPath string valid bool } // CreateFileLock sets up a directory containing the various lock files. func CreateFileLock(path string) (*FileLocks, error) { _, err := os.Stat(path) if err == nil { return nil, errors.Wrapf(syscall.EEXIST, "directory %s exists", path) } if err := os.MkdirAll(path, 0711); err != nil { return nil, errors.Wrapf(err, "cannot create %s", path) } locks := new(FileLocks) locks.lockPath = path locks.valid = true return locks, nil } // OpenFileLock opens an existing directory with the lock files. func OpenFileLock(path string) (*FileLocks, error) { _, err := os.Stat(path) if err != nil { return nil, errors.Wrapf(err, "accessing directory %s", path) } locks := new(FileLocks) locks.lockPath = path locks.valid = true return locks, nil } // Close closes an existing shared-memory segment. // The segment will be rendered unusable after closing. // WARNING: If you Close() while there are still locks locked, these locks may // fail to release, causing a program freeze. // Close() is only intended to be used while testing the locks. func (locks *FileLocks) Close() error { if !locks.valid { return errors.Wrapf(syscall.EINVAL, "locks have already been closed") } err := os.RemoveAll(locks.lockPath) if err != nil { return errors.Wrapf(err, "deleting directory %s", locks.lockPath) } return nil } func (locks *FileLocks) getLockPath(lck uint32) string { return filepath.Join(locks.lockPath, strconv.FormatInt(int64(lck), 10)) } // AllocateLock allocates a lock and returns the index of the lock that was allocated. func (locks *FileLocks) AllocateLock() (uint32, error) { if !locks.valid { return 0, errors.Wrapf(syscall.EINVAL, "locks have already been closed") } id := uint32(0) for ; ; id++ { path := locks.getLockPath(id) f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) if err != nil { if os.IsExist(err) { continue } return 0, errors.Wrapf(err, "creating lock file") } f.Close() break } return id, nil } // AllocateGivenLock allocates the given lock from the shared-memory // segment for use by a container or pod. // If the lock is already in use or the index is invalid an error will be // returned. func (locks *FileLocks) AllocateGivenLock(lck uint32) error { if !locks.valid { return errors.Wrapf(syscall.EINVAL, "locks have already been closed") } f, err := os.OpenFile(locks.getLockPath(lck), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) if err != nil { return errors.Wrapf(err, "error creating lock %d", lck) } f.Close() return nil } // DeallocateLock frees a lock in a shared-memory segment so it can be // reallocated to another container or pod. // The given lock must be already allocated, or an error will be returned. func (locks *FileLocks) DeallocateLock(lck uint32) error { if !locks.valid { return errors.Wrapf(syscall.EINVAL, "locks have already been closed") } if err := os.Remove(locks.getLockPath(lck)); err != nil { return errors.Wrapf(err, "deallocating lock %d", lck) } return nil } // DeallocateAllLocks frees all locks so they can be reallocated to // other containers and pods. func (locks *FileLocks) DeallocateAllLocks() error { if !locks.valid { return errors.Wrapf(syscall.EINVAL, "locks have already been closed") } files, err := ioutil.ReadDir(locks.lockPath) if err != nil { return errors.Wrapf(err, "error reading directory %s", locks.lockPath) } var lastErr error for _, f := range files { p := filepath.Join(locks.lockPath, f.Name()) err := os.Remove(p) if err != nil { lastErr = err logrus.Errorf("deallocating lock %s", p) } } return lastErr } // LockFileLock locks the given lock. func (locks *FileLocks) LockFileLock(lck uint32) error { if !locks.valid { return errors.Wrapf(syscall.EINVAL, "locks have already been closed") } l, err := storage.GetLockfile(locks.getLockPath(lck)) if err != nil { return errors.Wrapf(err, "error acquiring lock") } l.Lock() return nil } // UnlockFileLock unlocks the given lock. func (locks *FileLocks) UnlockFileLock(lck uint32) error { if !locks.valid { return errors.Wrapf(syscall.EINVAL, "locks have already been closed") } l, err := storage.GetLockfile(locks.getLockPath(lck)) if err != nil { return errors.Wrapf(err, "error acquiring lock") } l.Unlock() return nil }