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 }