mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-20 10:19:27 -05:00
Signed-off-by: Christian Mesh <christianmesh1@gmail.com> Signed-off-by: Martin Atkins <mart@degeneration.co.uk> Co-authored-by: Martin Atkins <mart@degeneration.co.uk>
102 lines
2.7 KiB
Go
102 lines
2.7 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
//go:build !windows
|
|
// +build !windows
|
|
|
|
package flock
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"syscall"
|
|
)
|
|
|
|
// use fcntl POSIX locks for the most consistent behavior across platforms, and
|
|
// hopefully some compatibility over NFS and CIFS.
|
|
func Lock(f *os.File) error {
|
|
flock := &syscall.Flock_t{
|
|
Type: syscall.F_RDLCK | syscall.F_WRLCK,
|
|
Whence: int16(io.SeekStart),
|
|
Start: 0,
|
|
Len: 0,
|
|
}
|
|
|
|
return syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, flock)
|
|
}
|
|
|
|
// LockBlocking is like Lock except that if the lock is currently contended
|
|
// then it blocks until it becomes available.
|
|
//
|
|
// If the given context is cancelled then it returns early with the cancellation
|
|
// error.
|
|
func LockBlocking(ctx context.Context, f *os.File) error {
|
|
flock := &syscall.Flock_t{
|
|
Type: syscall.F_RDLCK | syscall.F_WRLCK,
|
|
Whence: int16(io.SeekStart),
|
|
Start: 0,
|
|
Len: 0,
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
|
|
c := make(chan error)
|
|
go func() {
|
|
for {
|
|
err := syscall.FcntlFlock(f.Fd(), syscall.F_SETLKW, flock)
|
|
if err == syscall.EINTR {
|
|
// We'll get here if our process gets any signal at all, but
|
|
// not all signals represent cancellation.
|
|
if ctxErr := ctx.Err(); ctxErr != nil {
|
|
err = ctxErr // return the cancellation error instead of generic EINTR
|
|
} else {
|
|
continue // not cancelled yet
|
|
}
|
|
}
|
|
c <- err
|
|
close(c)
|
|
return
|
|
}
|
|
}()
|
|
|
|
for {
|
|
select {
|
|
case err := <-c:
|
|
return err
|
|
case <-ctx.Done():
|
|
// We will get here if the cancellation is caused by anything other
|
|
// than a Unix signal, in which case we'll send a signal ourselves
|
|
// to force the waiting goroutine to exit.
|
|
// We use SIGUSR1 here on the assumption that nothing else in
|
|
// OpenTofu uses it. We're sending this to the current pid
|
|
// explicitly because we might have other processes, such as
|
|
// plugins, also running in our process group (which is what we'd
|
|
// signal if using pid 0 here).
|
|
err := syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
|
|
if err != nil {
|
|
// This should not fail, but if it does then we'd otherwise
|
|
// get hung up here and so we'll return an error and accept
|
|
// that our background goroutine is going to just hang around
|
|
// until another signal shows up or the program exits.
|
|
return fmt.Errorf("failed canceling lock acquisition: %w", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func Unlock(f *os.File) error {
|
|
flock := &syscall.Flock_t{
|
|
Type: syscall.F_UNLCK,
|
|
Whence: int16(io.SeekStart),
|
|
Start: 0,
|
|
Len: 0,
|
|
}
|
|
|
|
return syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, flock)
|
|
}
|