github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/filesystem/locking/locker.go (about)

     1  package locking
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  )
     8  
     9  // Locker provides file locking facilities.
    10  type Locker struct {
    11  	// file is the underlying file object that's locked.
    12  	file *os.File
    13  	// held indicates whether or not the lock is currently held.
    14  	held bool
    15  }
    16  
    17  // NewLocker attempts to create a lock with the file at the specified path,
    18  // creating the file if necessary. The lock is returned in an unlocked state.
    19  func NewLocker(path string, permissions os.FileMode) (*Locker, error) {
    20  	mode := os.O_RDWR | os.O_CREATE | os.O_APPEND
    21  	if file, err := os.OpenFile(path, mode, permissions); err != nil {
    22  		return nil, fmt.Errorf("unable to open lock file: %w", err)
    23  	} else {
    24  		return &Locker{file: file}, nil
    25  	}
    26  }
    27  
    28  // Held returns whether or not the lock is currently held.
    29  func (l *Locker) Held() bool {
    30  	return l.held
    31  }
    32  
    33  // Read implements io.Reader.Read on the underlying file, but errors if the lock
    34  // is not currently held.
    35  func (l *Locker) Read(buffer []byte) (int, error) {
    36  	// Verify that the lock is held.
    37  	if !l.held {
    38  		return 0, errors.New("lock not held")
    39  	}
    40  
    41  	// Perform the read.
    42  	return l.file.Read(buffer)
    43  }
    44  
    45  // Write implements io.Writer.Write on the underlying file, but errors if the
    46  // lock is not currently held.
    47  func (l *Locker) Write(buffer []byte) (int, error) {
    48  	// Verify that the lock is held.
    49  	if !l.held {
    50  		return 0, errors.New("lock not held")
    51  	}
    52  
    53  	// Perform the write.
    54  	return l.file.Write(buffer)
    55  }
    56  
    57  // Truncate implements file truncation for the underlying file, but errors if
    58  // the lock is not currently held.
    59  func (l *Locker) Truncate(size int64) error {
    60  	// Verify that the lock is held.
    61  	if !l.held {
    62  		return errors.New("lock not held")
    63  	}
    64  
    65  	// Perform the truncation.
    66  	return l.file.Truncate(size)
    67  }
    68  
    69  // Close closes the file underlying the locker. This will release any lock held
    70  // on the file and disable future locking. On POSIX platforms, this also
    71  // releases other locks held on the same file.
    72  func (l *Locker) Close() error {
    73  	return l.file.Close()
    74  }