123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- // Copyright 2019 Tim Heckman. All rights reserved. Use of this source code is
- // governed by the BSD 3-Clause license that can be found in the LICENSE file.
-
- // Copyright 2018 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
-
- // This code implements the filelock API using POSIX 'fcntl' locks, which attach
- // to an (inode, process) pair rather than a file descriptor. To avoid unlocking
- // files prematurely when the same file is opened through different descriptors,
- // we allow only one read-lock at a time.
- //
- // This code is adapted from the Go package:
- // cmd/go/internal/lockedfile/internal/filelock
-
- //+build aix
-
- package flock
-
- import (
- "errors"
- "io"
- "os"
- "sync"
- "syscall"
-
- "golang.org/x/sys/unix"
- )
-
- type lockType int16
-
- const (
- readLock lockType = unix.F_RDLCK
- writeLock lockType = unix.F_WRLCK
- )
-
- type cmdType int
-
- const (
- tryLock cmdType = unix.F_SETLK
- waitLock cmdType = unix.F_SETLKW
- )
-
- type inode = uint64
-
- type inodeLock struct {
- owner *Flock
- queue []<-chan *Flock
- }
-
- var (
- mu sync.Mutex
- inodes = map[*Flock]inode{}
- locks = map[inode]inodeLock{}
- )
-
- // Lock is a blocking call to try and take an exclusive file lock. It will wait
- // until it is able to obtain the exclusive file lock. It's recommended that
- // TryLock() be used over this function. This function may block the ability to
- // query the current Locked() or RLocked() status due to a RW-mutex lock.
- //
- // If we are already exclusive-locked, this function short-circuits and returns
- // immediately assuming it can take the mutex lock.
- //
- // If the *Flock has a shared lock (RLock), this may transparently replace the
- // shared lock with an exclusive lock on some UNIX-like operating systems. Be
- // careful when using exclusive locks in conjunction with shared locks
- // (RLock()), because calling Unlock() may accidentally release the exclusive
- // lock that was once a shared lock.
- func (f *Flock) Lock() error {
- return f.lock(&f.l, writeLock)
- }
-
- // RLock is a blocking call to try and take a shared file lock. It will wait
- // until it is able to obtain the shared file lock. It's recommended that
- // TryRLock() be used over this function. This function may block the ability to
- // query the current Locked() or RLocked() status due to a RW-mutex lock.
- //
- // If we are already shared-locked, this function short-circuits and returns
- // immediately assuming it can take the mutex lock.
- func (f *Flock) RLock() error {
- return f.lock(&f.r, readLock)
- }
-
- func (f *Flock) lock(locked *bool, flag lockType) error {
- f.m.Lock()
- defer f.m.Unlock()
-
- if *locked {
- return nil
- }
-
- if f.fh == nil {
- if err := f.setFh(); err != nil {
- return err
- }
- defer f.ensureFhState()
- }
-
- if _, err := f.doLock(waitLock, flag, true); err != nil {
- return err
- }
-
- *locked = true
- return nil
- }
-
- func (f *Flock) doLock(cmd cmdType, lt lockType, blocking bool) (bool, error) {
- // POSIX locks apply per inode and process, and the lock for an inode is
- // released when *any* descriptor for that inode is closed. So we need to
- // synchronize access to each inode internally, and must serialize lock and
- // unlock calls that refer to the same inode through different descriptors.
- fi, err := f.fh.Stat()
- if err != nil {
- return false, err
- }
- ino := inode(fi.Sys().(*syscall.Stat_t).Ino)
-
- mu.Lock()
- if i, dup := inodes[f]; dup && i != ino {
- mu.Unlock()
- return false, &os.PathError{
- Path: f.Path(),
- Err: errors.New("inode for file changed since last Lock or RLock"),
- }
- }
-
- inodes[f] = ino
-
- var wait chan *Flock
- l := locks[ino]
- if l.owner == f {
- // This file already owns the lock, but the call may change its lock type.
- } else if l.owner == nil {
- // No owner: it's ours now.
- l.owner = f
- } else if !blocking {
- // Already owned: cannot take the lock.
- mu.Unlock()
- return false, nil
- } else {
- // Already owned: add a channel to wait on.
- wait = make(chan *Flock)
- l.queue = append(l.queue, wait)
- }
- locks[ino] = l
- mu.Unlock()
-
- if wait != nil {
- wait <- f
- }
-
- err = setlkw(f.fh.Fd(), cmd, lt)
-
- if err != nil {
- f.doUnlock()
- if cmd == tryLock && err == unix.EACCES {
- return false, nil
- }
- return false, err
- }
-
- return true, nil
- }
-
- func (f *Flock) Unlock() error {
- f.m.Lock()
- defer f.m.Unlock()
-
- // if we aren't locked or if the lockfile instance is nil
- // just return a nil error because we are unlocked
- if (!f.l && !f.r) || f.fh == nil {
- return nil
- }
-
- if err := f.doUnlock(); err != nil {
- return err
- }
-
- f.fh.Close()
-
- f.l = false
- f.r = false
- f.fh = nil
-
- return nil
- }
-
- func (f *Flock) doUnlock() (err error) {
- var owner *Flock
- mu.Lock()
- ino, ok := inodes[f]
- if ok {
- owner = locks[ino].owner
- }
- mu.Unlock()
-
- if owner == f {
- err = setlkw(f.fh.Fd(), waitLock, unix.F_UNLCK)
- }
-
- mu.Lock()
- l := locks[ino]
- if len(l.queue) == 0 {
- // No waiters: remove the map entry.
- delete(locks, ino)
- } else {
- // The first waiter is sending us their file now.
- // Receive it and update the queue.
- l.owner = <-l.queue[0]
- l.queue = l.queue[1:]
- locks[ino] = l
- }
- delete(inodes, f)
- mu.Unlock()
-
- return err
- }
-
- // TryLock is the preferred function for taking an exclusive file lock. This
- // function takes an RW-mutex lock before it tries to lock the file, so there is
- // the possibility that this function may block for a short time if another
- // goroutine is trying to take any action.
- //
- // The actual file lock is non-blocking. If we are unable to get the exclusive
- // file lock, the function will return false instead of waiting for the lock. If
- // we get the lock, we also set the *Flock instance as being exclusive-locked.
- func (f *Flock) TryLock() (bool, error) {
- return f.try(&f.l, writeLock)
- }
-
- // TryRLock is the preferred function for taking a shared file lock. This
- // function takes an RW-mutex lock before it tries to lock the file, so there is
- // the possibility that this function may block for a short time if another
- // goroutine is trying to take any action.
- //
- // The actual file lock is non-blocking. If we are unable to get the shared file
- // lock, the function will return false instead of waiting for the lock. If we
- // get the lock, we also set the *Flock instance as being share-locked.
- func (f *Flock) TryRLock() (bool, error) {
- return f.try(&f.r, readLock)
- }
-
- func (f *Flock) try(locked *bool, flag lockType) (bool, error) {
- f.m.Lock()
- defer f.m.Unlock()
-
- if *locked {
- return true, nil
- }
-
- if f.fh == nil {
- if err := f.setFh(); err != nil {
- return false, err
- }
- defer f.ensureFhState()
- }
-
- haslock, err := f.doLock(tryLock, flag, false)
- if err != nil {
- return false, err
- }
-
- *locked = haslock
- return haslock, nil
- }
-
- // setlkw calls FcntlFlock with cmd for the entire file indicated by fd.
- func setlkw(fd uintptr, cmd cmdType, lt lockType) error {
- for {
- err := unix.FcntlFlock(fd, int(cmd), &unix.Flock_t{
- Type: int16(lt),
- Whence: io.SeekStart,
- Start: 0,
- Len: 0, // All bytes.
- })
- if err != unix.EINTR {
- return err
- }
- }
- }
|