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 }