You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

flock_aix.go 6.9KB

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