github.com/thy00/storage@v1.12.8/lockfile_unix.go (about) 1 // +build linux solaris darwin freebsd 2 3 package storage 4 5 import ( 6 "fmt" 7 "os" 8 "sync" 9 "time" 10 11 "github.com/containers/storage/pkg/stringid" 12 "github.com/pkg/errors" 13 "golang.org/x/sys/unix" 14 ) 15 16 type lockfile struct { 17 // rwMutex serializes concurrent reader-writer acquisitions in the same process space 18 rwMutex *sync.RWMutex 19 // stateMutex is used to synchronize concurrent accesses to the state below 20 stateMutex *sync.Mutex 21 counter int64 22 file string 23 fd uintptr 24 lw string 25 locktype int16 26 locked bool 27 ro bool 28 recursive bool 29 } 30 31 // openLock opens the file at path and returns the corresponding file 32 // descriptor. Note that the path is opened read-only when ro is set. If ro 33 // is unset, openLock will open the path read-write and create the file if 34 // necessary. 35 func openLock(path string, ro bool) (int, error) { 36 if ro { 37 return unix.Open(path, os.O_RDONLY, 0) 38 } 39 return unix.Open(path, os.O_RDWR|os.O_CREATE, unix.S_IRUSR|unix.S_IWUSR) 40 } 41 42 // createLockerForPath returns a Locker object, possibly (depending on the platform) 43 // working inter-process and associated with the specified path. 44 // 45 // This function will be called at most once for each path value within a single process. 46 // 47 // If ro, the lock is a read-write lock and the returned Locker should correspond to the 48 // “lock for reading” (shared) operation; otherwise, the lock is either an exclusive lock, 49 // or a read-write lock and Locker should correspond to the “lock for writing” (exclusive) operation. 50 // 51 // WARNING: 52 // - The lock may or MAY NOT be inter-process. 53 // - There may or MAY NOT be an actual object on the filesystem created for the specified path. 54 // - Even if ro, the lock MAY be exclusive. 55 func createLockerForPath(path string, ro bool) (Locker, error) { 56 // Check if we can open the lock. 57 fd, err := openLock(path, ro) 58 if err != nil { 59 return nil, errors.Wrapf(err, "error opening %q", path) 60 } 61 unix.Close(fd) 62 63 locktype := unix.F_WRLCK 64 if ro { 65 locktype = unix.F_RDLCK 66 } 67 return &lockfile{ 68 stateMutex: &sync.Mutex{}, 69 rwMutex: &sync.RWMutex{}, 70 file: path, 71 lw: stringid.GenerateRandomID(), 72 locktype: int16(locktype), 73 locked: false, 74 ro: ro}, nil 75 } 76 77 // lock locks the lockfile via FCTNL(2) based on the specified type and 78 // command. 79 func (l *lockfile) lock(l_type int16, recursive bool) { 80 lk := unix.Flock_t{ 81 Type: l_type, 82 Whence: int16(os.SEEK_SET), 83 Start: 0, 84 Len: 0, 85 } 86 switch l_type { 87 case unix.F_RDLCK: 88 l.rwMutex.RLock() 89 case unix.F_WRLCK: 90 if recursive { 91 // NOTE: that's okay as recursive is only set in RecursiveLock(), so 92 // there's no need to protect against hypothetical RDLCK cases. 93 l.rwMutex.RLock() 94 } else { 95 l.rwMutex.Lock() 96 } 97 default: 98 panic(fmt.Sprintf("attempted to acquire a file lock of unrecognized type %d", l_type)) 99 } 100 l.stateMutex.Lock() 101 defer l.stateMutex.Unlock() 102 if l.counter == 0 { 103 // If we're the first reference on the lock, we need to open the file again. 104 fd, err := openLock(l.file, l.ro) 105 if err != nil { 106 panic(fmt.Sprintf("error opening %q", l.file)) 107 } 108 unix.CloseOnExec(fd) 109 l.fd = uintptr(fd) 110 111 // Optimization: only use the (expensive) fcntl syscall when 112 // the counter is 0. In this case, we're either the first 113 // reader lock or a writer lock. 114 for unix.FcntlFlock(l.fd, unix.F_SETLKW, &lk) != nil { 115 time.Sleep(10 * time.Millisecond) 116 } 117 } 118 l.locktype = l_type 119 l.locked = true 120 l.recursive = recursive 121 l.counter++ 122 } 123 124 // Lock locks the lockfile as a writer. Note that RLock() will be called if 125 // the lock is a read-only one. 126 func (l *lockfile) Lock() { 127 if l.ro { 128 l.RLock() 129 } else { 130 l.lock(unix.F_WRLCK, false) 131 } 132 } 133 134 // RecursiveLock locks the lockfile as a writer but allows for recursive 135 // acquisitions within the same process space. Note that RLock() will be called 136 // if it's a lockTypReader lock. 137 func (l *lockfile) RecursiveLock() { 138 if l.ro { 139 l.RLock() 140 } else { 141 l.lock(unix.F_WRLCK, true) 142 } 143 } 144 145 // LockRead locks the lockfile as a reader. 146 func (l *lockfile) RLock() { 147 l.lock(unix.F_RDLCK, false) 148 } 149 150 // Unlock unlocks the lockfile. 151 func (l *lockfile) Unlock() { 152 lk := unix.Flock_t{ 153 Type: unix.F_UNLCK, 154 Whence: int16(os.SEEK_SET), 155 Start: 0, 156 Len: 0, 157 Pid: int32(os.Getpid()), 158 } 159 l.stateMutex.Lock() 160 if l.locked == false { 161 // Panic when unlocking an unlocked lock. That's a violation 162 // of the lock semantics and will reveal such. 163 panic("calling Unlock on unlocked lock") 164 } 165 l.counter-- 166 if l.counter < 0 { 167 // Panic when the counter is negative. There is no way we can 168 // recover from a corrupted lock and we need to protect the 169 // storage from corruption. 170 panic(fmt.Sprintf("lock %q has been unlocked too often", l.file)) 171 } 172 if l.counter == 0 { 173 // We should only release the lock when the counter is 0 to 174 // avoid releasing read-locks too early; a given process may 175 // acquire a read lock multiple times. 176 l.locked = false 177 for unix.FcntlFlock(l.fd, unix.F_SETLKW, &lk) != nil { 178 time.Sleep(10 * time.Millisecond) 179 } 180 // Close the file descriptor on the last unlock. 181 unix.Close(int(l.fd)) 182 } 183 if l.locktype == unix.F_RDLCK || l.recursive { 184 l.rwMutex.RUnlock() 185 } else { 186 l.rwMutex.Unlock() 187 } 188 l.stateMutex.Unlock() 189 } 190 191 // Locked checks if lockfile is locked for writing by a thread in this process. 192 func (l *lockfile) Locked() bool { 193 l.stateMutex.Lock() 194 defer l.stateMutex.Unlock() 195 return l.locked && (l.locktype == unix.F_WRLCK) 196 } 197 198 // Touch updates the lock file with the UID of the user. 199 func (l *lockfile) Touch() error { 200 l.stateMutex.Lock() 201 if !l.locked || (l.locktype != unix.F_WRLCK) { 202 panic("attempted to update last-writer in lockfile without the write lock") 203 } 204 l.stateMutex.Unlock() 205 l.lw = stringid.GenerateRandomID() 206 id := []byte(l.lw) 207 _, err := unix.Seek(int(l.fd), 0, os.SEEK_SET) 208 if err != nil { 209 return err 210 } 211 n, err := unix.Write(int(l.fd), id) 212 if err != nil { 213 return err 214 } 215 if n != len(id) { 216 return unix.ENOSPC 217 } 218 err = unix.Fsync(int(l.fd)) 219 if err != nil { 220 return err 221 } 222 return nil 223 } 224 225 // Modified indicates if the lockfile has been updated since the last time it 226 // was loaded. 227 func (l *lockfile) Modified() (bool, error) { 228 id := []byte(l.lw) 229 l.stateMutex.Lock() 230 if !l.locked { 231 panic("attempted to check last-writer in lockfile without locking it first") 232 } 233 l.stateMutex.Unlock() 234 _, err := unix.Seek(int(l.fd), 0, os.SEEK_SET) 235 if err != nil { 236 return true, err 237 } 238 n, err := unix.Read(int(l.fd), id) 239 if err != nil { 240 return true, err 241 } 242 if n != len(id) { 243 return true, nil 244 } 245 lw := l.lw 246 l.lw = string(id) 247 return l.lw != lw, nil 248 } 249 250 // IsReadWriteLock indicates if the lock file is a read-write lock. 251 func (l *lockfile) IsReadWrite() bool { 252 return !l.ro 253 }