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