github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/go/not-internal/lockedfile/internal/filelock/filelock_fcntl.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // +build aix solaris
     6  
     7  // This code implements the filelock API using POSIX 'fcntl' locks, which attach
     8  // to an (inode, process) pair rather than a file descriptor. To avoid unlocking
     9  // files prematurely when the same file is opened through different descriptors,
    10  // we allow only one read-lock at a time.
    11  //
    12  // Most platforms provide some alternative API, such as an 'flock' system call
    13  // or an F_OFD_SETLK command for 'fcntl', that allows for better concurrency and
    14  // does not require per-inode bookkeeping in the application.
    15  //
    16  // TODO(bcmills): If we add a build tag for Illumos (see golang.org/issue/20603)
    17  // then Illumos should use F_OFD_SETLK, and the resulting code would be as
    18  // simple as filelock_unix.go. We will still need the code in this file for AIX
    19  // or as long as Oracle Solaris provides only F_SETLK.
    20  
    21  package filelock
    22  
    23  import (
    24  	"errors"
    25  	"io"
    26  	"os"
    27  	"sync"
    28  	"syscall"
    29  )
    30  
    31  type lockType int16
    32  
    33  const (
    34  	readLock  lockType = syscall.F_RDLCK
    35  	writeLock lockType = syscall.F_WRLCK
    36  )
    37  
    38  type inode = uint64 // type of syscall.Stat_t.Ino
    39  
    40  type inodeLock struct {
    41  	owner File
    42  	queue []<-chan File
    43  }
    44  
    45  type token struct{}
    46  
    47  var (
    48  	mu     sync.Mutex
    49  	inodes = map[File]inode{}
    50  	locks  = map[inode]inodeLock{}
    51  )
    52  
    53  func lock(f File, lt lockType) (err error) {
    54  	// POSIX locks apply per inode and process, and the lock for an inode is
    55  	// released when *any* descriptor for that inode is closed. So we need to
    56  	// synchronize access to each inode internally, and must serialize lock and
    57  	// unlock calls that refer to the same inode through different descriptors.
    58  	fi, err := f.Stat()
    59  	if err != nil {
    60  		return err
    61  	}
    62  	ino := fi.Sys().(*syscall.Stat_t).Ino
    63  
    64  	mu.Lock()
    65  	if i, dup := inodes[f]; dup && i != ino {
    66  		mu.Unlock()
    67  		return &os.PathError{
    68  			Op:   lt.String(),
    69  			Path: f.Name(),
    70  			Err:  errors.New("inode for file changed since last Lock or RLock"),
    71  		}
    72  	}
    73  	inodes[f] = ino
    74  
    75  	var wait chan File
    76  	l := locks[ino]
    77  	if l.owner == f {
    78  		// This file already owns the lock, but the call may change its lock type.
    79  	} else if l.owner == nil {
    80  		// No owner: it's ours now.
    81  		l.owner = f
    82  	} else {
    83  		// Already owned: add a channel to wait on.
    84  		wait = make(chan File)
    85  		l.queue = append(l.queue, wait)
    86  	}
    87  	locks[ino] = l
    88  	mu.Unlock()
    89  
    90  	if wait != nil {
    91  		wait <- f
    92  	}
    93  
    94  	err = setlkw(f.Fd(), lt)
    95  
    96  	if err != nil {
    97  		unlock(f)
    98  		return &os.PathError{
    99  			Op:   lt.String(),
   100  			Path: f.Name(),
   101  			Err:  err,
   102  		}
   103  	}
   104  
   105  	return nil
   106  }
   107  
   108  func unlock(f File) error {
   109  	var owner File
   110  
   111  	mu.Lock()
   112  	ino, ok := inodes[f]
   113  	if ok {
   114  		owner = locks[ino].owner
   115  	}
   116  	mu.Unlock()
   117  
   118  	if owner != f {
   119  		panic("unlock called on a file that is not locked")
   120  	}
   121  
   122  	err := setlkw(f.Fd(), syscall.F_UNLCK)
   123  
   124  	mu.Lock()
   125  	l := locks[ino]
   126  	if len(l.queue) == 0 {
   127  		// No waiters: remove the map entry.
   128  		delete(locks, ino)
   129  	} else {
   130  		// The first waiter is sending us their file now.
   131  		// Receive it and update the queue.
   132  		l.owner = <-l.queue[0]
   133  		l.queue = l.queue[1:]
   134  		locks[ino] = l
   135  	}
   136  	delete(inodes, f)
   137  	mu.Unlock()
   138  
   139  	return err
   140  }
   141  
   142  // setlkw calls FcntlFlock with F_SETLKW for the entire file indicated by fd.
   143  func setlkw(fd uintptr, lt lockType) error {
   144  	for {
   145  		err := syscall.FcntlFlock(fd, syscall.F_SETLKW, &syscall.Flock_t{
   146  			Type:   int16(lt),
   147  			Whence: io.SeekStart,
   148  			Start:  0,
   149  			Len:    0, // All bytes.
   150  		})
   151  		if err != syscall.EINTR {
   152  			return err
   153  		}
   154  	}
   155  }
   156  
   157  func isNotSupported(err error) bool {
   158  	return err == syscall.ENOSYS || err == syscall.ENOTSUP || err == syscall.EOPNOTSUPP || err == ErrNotSupported
   159  }