github.com/cilium/cilium@v1.16.2/pkg/lock/lockfile/lockfile_linux.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 package lockfile 4 5 import ( 6 "context" 7 "fmt" 8 "os" 9 "syscall" 10 ) 11 12 // Lockfile is a simple wrapper around POSIX file locking 13 // but it uses Linux's per-fd locks, which makes it safe to 14 // use within the same process 15 type Lockfile struct { 16 fp *os.File 17 } 18 19 // Linux supports per-file-descriptor locks, which are safer 20 // and, more importantly, testable 21 22 const ( 23 SETLK = 37 // F_OFD_SETLK 24 SETLKW = 38 // F_OFD_SETLKW 25 ) 26 27 // NewLockfile creates and opens a lockfile, but does not acquire 28 // a lock. 29 func NewLockfile(path string) (*Lockfile, error) { 30 fp, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644) 31 if err != nil { 32 return nil, fmt.Errorf("failed to create lockfile %s: %w", path, err) 33 } 34 35 return &Lockfile{ 36 fp: fp, 37 }, nil 38 } 39 40 // Close will close the file, which implicitly removes all locks held. 41 // It is an error to re-use a closed Lockfile. 42 func (l *Lockfile) Close() error { 43 fp := l.fp 44 l.fp = nil 45 return fp.Close() 46 } 47 48 // TryLock will attempt to take a lock, returining error if it is not 49 // possible to acquire the lock. 50 // If exclusive is true, then it will attempt to obtain a write, or exclusive, lock 51 func (l *Lockfile) TryLock(exclusive bool) error { 52 return l.flock(context.Background(), false, exclusive, false) 53 } 54 55 // Lock will attempt to take a lock, blocking until it is able to do so. 56 // If exclusive is true, then it will obtain a write, or exclusive, lock 57 func (l *Lockfile) Lock(ctx context.Context, exclusive bool) error { 58 return l.flock(ctx, false, exclusive, true) 59 } 60 61 // Unlock removes the lock, but keeps the file open. 62 func (l *Lockfile) Unlock() error { 63 return l.flock(context.Background(), true, false, false) 64 } 65 66 // flock will perform the lock operation 67 // - unlock: if true, remove the lock 68 // - exclusive: if true, then obtain a write lock, else a read lock 69 // - wait: if true, then block until the lock is obtained. Ignored when unlocking 70 func (l *Lockfile) flock(ctx context.Context, unlock, exclusive, wait bool) error { 71 var lockType int16 = syscall.F_RDLCK 72 if unlock { 73 lockType = syscall.F_UNLCK // unlock is a lockType!? What an API. 74 } else if exclusive { 75 lockType = syscall.F_WRLCK 76 } 77 78 command := SETLK 79 if !unlock && wait { 80 command = SETLKW 81 } 82 83 flockT := syscall.Flock_t{ 84 Type: lockType, 85 Whence: 0, 86 Start: 0, 87 Len: 0, 88 } 89 90 // if no context is supplied, or the context is non-cancellable, 91 // then don't do the goroutine dance 92 if ctx.Done() == nil { 93 return syscall.FcntlFlock(l.fp.Fd(), command, &flockT) 94 } 95 96 // syscalls can't be cancelled, so just wrap in a goroutine so we can 97 // return early if the context closes 98 lockCh := make(chan error, 1) 99 go func() { 100 lockCh <- syscall.FcntlFlock(l.fp.Fd(), command, &flockT) 101 }() 102 select { 103 case err := <-lockCh: 104 return err 105 case <-ctx.Done(): 106 // oops, we cancelled 107 // spin up a goroutine to drop the lock when we get it, 108 // since syscalls can't actually be cancelled 109 go func() { 110 err := <-lockCh 111 if err == nil { 112 l.flock(context.Background(), true, false, false) 113 } 114 }() 115 return ctx.Err() 116 } 117 }