github.com/containers/podman/v4@v4.9.4/libpod/lock/file/file_lock.go (about)

     1  package file
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strconv"
     8  	"syscall"
     9  
    10  	"github.com/containers/storage/pkg/lockfile"
    11  	"github.com/sirupsen/logrus"
    12  )
    13  
    14  // FileLocks is a struct enabling POSIX lock locking in a shared memory
    15  // segment.
    16  type FileLocks struct { //nolint:revive // struct name stutters
    17  	lockPath string
    18  	valid    bool
    19  }
    20  
    21  // CreateFileLock sets up a directory containing the various lock files.
    22  func CreateFileLock(path string) (*FileLocks, error) {
    23  	_, err := os.Stat(path)
    24  	if err == nil {
    25  		return nil, fmt.Errorf("directory %s exists: %w", path, syscall.EEXIST)
    26  	}
    27  	if err := os.MkdirAll(path, 0711); err != nil {
    28  		return nil, err
    29  	}
    30  
    31  	locks := new(FileLocks)
    32  	locks.lockPath = path
    33  	locks.valid = true
    34  
    35  	return locks, nil
    36  }
    37  
    38  // OpenFileLock opens an existing directory with the lock files.
    39  func OpenFileLock(path string) (*FileLocks, error) {
    40  	_, err := os.Stat(path)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  
    45  	locks := new(FileLocks)
    46  	locks.lockPath = path
    47  	locks.valid = true
    48  
    49  	return locks, nil
    50  }
    51  
    52  // Close closes an existing shared-memory segment.
    53  // The segment will be rendered unusable after closing.
    54  // WARNING: If you Close() while there are still locks locked, these locks may
    55  // fail to release, causing a program freeze.
    56  // Close() is only intended to be used while testing the locks.
    57  func (locks *FileLocks) Close() error {
    58  	if !locks.valid {
    59  		return fmt.Errorf("locks have already been closed: %w", syscall.EINVAL)
    60  	}
    61  	err := os.RemoveAll(locks.lockPath)
    62  	if err != nil {
    63  		return fmt.Errorf("deleting directory %s: %w", locks.lockPath, err)
    64  	}
    65  	return nil
    66  }
    67  
    68  func (locks *FileLocks) getLockPath(lck uint32) string {
    69  	return filepath.Join(locks.lockPath, strconv.FormatInt(int64(lck), 10))
    70  }
    71  
    72  // AllocateLock allocates a lock and returns the index of the lock that was allocated.
    73  func (locks *FileLocks) AllocateLock() (uint32, error) {
    74  	if !locks.valid {
    75  		return 0, fmt.Errorf("locks have already been closed: %w", syscall.EINVAL)
    76  	}
    77  
    78  	id := uint32(0)
    79  	for ; ; id++ {
    80  		path := locks.getLockPath(id)
    81  		f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
    82  		if err != nil {
    83  			if os.IsExist(err) {
    84  				continue
    85  			}
    86  			return 0, fmt.Errorf("creating lock file: %w", err)
    87  		}
    88  		f.Close()
    89  		break
    90  	}
    91  	return id, nil
    92  }
    93  
    94  // AllocateGivenLock allocates the given lock from the shared-memory
    95  // segment for use by a container or pod.
    96  // If the lock is already in use or the index is invalid an error will be
    97  // returned.
    98  func (locks *FileLocks) AllocateGivenLock(lck uint32) error {
    99  	if !locks.valid {
   100  		return fmt.Errorf("locks have already been closed: %w", syscall.EINVAL)
   101  	}
   102  
   103  	f, err := os.OpenFile(locks.getLockPath(lck), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
   104  	if err != nil {
   105  		return fmt.Errorf("creating lock %d: %w", lck, err)
   106  	}
   107  	f.Close()
   108  
   109  	return nil
   110  }
   111  
   112  // DeallocateLock frees a lock in a shared-memory segment so it can be
   113  // reallocated to another container or pod.
   114  // The given lock must be already allocated, or an error will be returned.
   115  func (locks *FileLocks) DeallocateLock(lck uint32) error {
   116  	if !locks.valid {
   117  		return fmt.Errorf("locks have already been closed: %w", syscall.EINVAL)
   118  	}
   119  	if err := os.Remove(locks.getLockPath(lck)); err != nil {
   120  		return fmt.Errorf("deallocating lock %d: %w", lck, err)
   121  	}
   122  	return nil
   123  }
   124  
   125  // DeallocateAllLocks frees all locks so they can be reallocated to
   126  // other containers and pods.
   127  func (locks *FileLocks) DeallocateAllLocks() error {
   128  	if !locks.valid {
   129  		return fmt.Errorf("locks have already been closed: %w", syscall.EINVAL)
   130  	}
   131  	files, err := os.ReadDir(locks.lockPath)
   132  	if err != nil {
   133  		return fmt.Errorf("reading directory %s: %w", locks.lockPath, err)
   134  	}
   135  	var lastErr error
   136  	for _, f := range files {
   137  		p := filepath.Join(locks.lockPath, f.Name())
   138  		err := os.Remove(p)
   139  		if err != nil {
   140  			lastErr = err
   141  			logrus.Errorf("Deallocating lock %s", p)
   142  		}
   143  	}
   144  	return lastErr
   145  }
   146  
   147  // LockFileLock locks the given lock.
   148  func (locks *FileLocks) LockFileLock(lck uint32) error {
   149  	if !locks.valid {
   150  		return fmt.Errorf("locks have already been closed: %w", syscall.EINVAL)
   151  	}
   152  
   153  	l, err := lockfile.GetLockFile(locks.getLockPath(lck))
   154  	if err != nil {
   155  		return fmt.Errorf("acquiring lock: %w", err)
   156  	}
   157  
   158  	l.Lock()
   159  	return nil
   160  }
   161  
   162  // UnlockFileLock unlocks the given lock.
   163  func (locks *FileLocks) UnlockFileLock(lck uint32) error {
   164  	if !locks.valid {
   165  		return fmt.Errorf("locks have already been closed: %w", syscall.EINVAL)
   166  	}
   167  	l, err := lockfile.GetLockFile(locks.getLockPath(lck))
   168  	if err != nil {
   169  		return fmt.Errorf("acquiring lock: %w", err)
   170  	}
   171  
   172  	l.Unlock()
   173  	return nil
   174  }