1// Copyright 2019 Tim Heckman. All rights reserved. Use of this source code is 2// governed by the BSD 3-Clause license that can be found in the LICENSE file. 3 4// Copyright 2018 The Go Authors. All rights reserved. 5// Use of this source code is governed by a BSD-style 6// license that can be found in the LICENSE file. 7 8// This code implements the filelock API using POSIX 'fcntl' locks, which attach 9// to an (inode, process) pair rather than a file descriptor. To avoid unlocking 10// files prematurely when the same file is opened through different descriptors, 11// we allow only one read-lock at a time. 12// 13// This code is adapted from the Go package: 14// cmd/go/internal/lockedfile/internal/filelock 15 16//+build aix 17 18package flock 19 20import ( 21 "errors" 22 "io" 23 "os" 24 "sync" 25 "syscall" 26 27 "golang.org/x/sys/unix" 28) 29 30type lockType int16 31 32const ( 33 readLock lockType = unix.F_RDLCK 34 writeLock lockType = unix.F_WRLCK 35) 36 37type inode = uint64 38 39type inodeLock struct { 40 owner *Flock 41 queue []<-chan *Flock 42} 43 44var ( 45 mu sync.Mutex 46 inodes = map[*Flock]inode{} 47 locks = map[inode]inodeLock{} 48) 49 50// Lock is a blocking call to try and take an exclusive file lock. It will wait 51// until it is able to obtain the exclusive file lock. It's recommended that 52// TryLock() be used over this function. This function may block the ability to 53// query the current Locked() or RLocked() status due to a RW-mutex lock. 54// 55// If we are already exclusive-locked, this function short-circuits and returns 56// immediately assuming it can take the mutex lock. 57// 58// If the *Flock has a shared lock (RLock), this may transparently replace the 59// shared lock with an exclusive lock on some UNIX-like operating systems. Be 60// careful when using exclusive locks in conjunction with shared locks 61// (RLock()), because calling Unlock() may accidentally release the exclusive 62// lock that was once a shared lock. 63func (f *Flock) Lock() error { 64 return f.lock(&f.l, writeLock) 65} 66 67// RLock is a blocking call to try and take a shared file lock. It will wait 68// until it is able to obtain the shared file lock. It's recommended that 69// TryRLock() be used over this function. This function may block the ability to 70// query the current Locked() or RLocked() status due to a RW-mutex lock. 71// 72// If we are already shared-locked, this function short-circuits and returns 73// immediately assuming it can take the mutex lock. 74func (f *Flock) RLock() error { 75 return f.lock(&f.r, readLock) 76} 77 78func (f *Flock) lock(locked *bool, flag lockType) error { 79 f.m.Lock() 80 defer f.m.Unlock() 81 82 if *locked { 83 return nil 84 } 85 86 if f.fh == nil { 87 if err := f.setFh(); err != nil { 88 return err 89 } 90 defer f.ensureFhState() 91 } 92 93 if _, err := f.doLock(flag, true); err != nil { 94 return err 95 } 96 97 *locked = true 98 return nil 99} 100 101func (f *Flock) doLock(lt lockType, blocking bool) (bool, error) { 102 // POSIX locks apply per inode and process, and the lock for an inode is 103 // released when *any* descriptor for that inode is closed. So we need to 104 // synchronize access to each inode internally, and must serialize lock and 105 // unlock calls that refer to the same inode through different descriptors. 106 fi, err := f.fh.Stat() 107 if err != nil { 108 return false, err 109 } 110 ino := inode(fi.Sys().(*syscall.Stat_t).Ino) 111 112 mu.Lock() 113 if i, dup := inodes[f]; dup && i != ino { 114 mu.Unlock() 115 return false, &os.PathError{ 116 Path: f.Path(), 117 Err: errors.New("inode for file changed since last Lock or RLock"), 118 } 119 } 120 121 inodes[f] = ino 122 123 var wait chan *Flock 124 l := locks[ino] 125 if l.owner == f { 126 // This file already owns the lock, but the call may change its lock type. 127 } else if l.owner == nil { 128 // No owner: it's ours now. 129 l.owner = f 130 } else if !blocking { 131 // Already owned: cannot take the lock. 132 mu.Unlock() 133 return false, nil 134 } else { 135 // Already owned: add a channel to wait on. 136 wait = make(chan *Flock) 137 l.queue = append(l.queue, wait) 138 } 139 locks[ino] = l 140 mu.Unlock() 141 142 if wait != nil { 143 wait <- f 144 } 145 146 err = setlkw(f.fh.Fd(), lt) 147 148 if err != nil { 149 f.doUnlock() 150 return false, err 151 } 152 153 return true, nil 154} 155 156func (f *Flock) Unlock() error { 157 f.m.Lock() 158 defer f.m.Unlock() 159 160 // if we aren't locked or if the lockfile instance is nil 161 // just return a nil error because we are unlocked 162 if (!f.l && !f.r) || f.fh == nil { 163 return nil 164 } 165 166 if err := f.doUnlock(); err != nil { 167 return err 168 } 169 170 f.fh.Close() 171 172 f.l = false 173 f.r = false 174 f.fh = nil 175 176 return nil 177} 178 179func (f *Flock) doUnlock() (err error) { 180 var owner *Flock 181 mu.Lock() 182 ino, ok := inodes[f] 183 if ok { 184 owner = locks[ino].owner 185 } 186 mu.Unlock() 187 188 if owner == f { 189 err = setlkw(f.fh.Fd(), unix.F_UNLCK) 190 } 191 192 mu.Lock() 193 l := locks[ino] 194 if len(l.queue) == 0 { 195 // No waiters: remove the map entry. 196 delete(locks, ino) 197 } else { 198 // The first waiter is sending us their file now. 199 // Receive it and update the queue. 200 l.owner = <-l.queue[0] 201 l.queue = l.queue[1:] 202 locks[ino] = l 203 } 204 delete(inodes, f) 205 mu.Unlock() 206 207 return err 208} 209 210// TryLock is the preferred function for taking an exclusive file lock. This 211// function takes an RW-mutex lock before it tries to lock the file, so there is 212// the possibility that this function may block for a short time if another 213// goroutine is trying to take any action. 214// 215// The actual file lock is non-blocking. If we are unable to get the exclusive 216// file lock, the function will return false instead of waiting for the lock. If 217// we get the lock, we also set the *Flock instance as being exclusive-locked. 218func (f *Flock) TryLock() (bool, error) { 219 return f.try(&f.l, writeLock) 220} 221 222// TryRLock is the preferred function for taking a shared file lock. This 223// function takes an RW-mutex lock before it tries to lock the file, so there is 224// the possibility that this function may block for a short time if another 225// goroutine is trying to take any action. 226// 227// The actual file lock is non-blocking. If we are unable to get the shared file 228// lock, the function will return false instead of waiting for the lock. If we 229// get the lock, we also set the *Flock instance as being share-locked. 230func (f *Flock) TryRLock() (bool, error) { 231 return f.try(&f.r, readLock) 232} 233 234func (f *Flock) try(locked *bool, flag lockType) (bool, error) { 235 f.m.Lock() 236 defer f.m.Unlock() 237 238 if *locked { 239 return true, nil 240 } 241 242 if f.fh == nil { 243 if err := f.setFh(); err != nil { 244 return false, err 245 } 246 defer f.ensureFhState() 247 } 248 249 haslock, err := f.doLock(flag, false) 250 if err != nil { 251 return false, err 252 } 253 254 *locked = haslock 255 return haslock, nil 256} 257 258// setlkw calls FcntlFlock with F_SETLKW for the entire file indicated by fd. 259func setlkw(fd uintptr, lt lockType) error { 260 for { 261 err := unix.FcntlFlock(fd, unix.F_SETLKW, &unix.Flock_t{ 262 Type: int16(lt), 263 Whence: io.SeekStart, 264 Start: 0, 265 Len: 0, // All bytes. 266 }) 267 if err != unix.EINTR { 268 return err 269 } 270 } 271} 272