github.com/sshnaidm/storage@v1.12.13/lockfile_unix.go (about)

     1  // +build linux solaris darwin freebsd
     2  
     3  package storage
     4  
     5  import (
     6  	"fmt"
     7  	"os"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/containers/storage/pkg/stringid"
    12  	"github.com/containers/storage/pkg/system"
    13  	"github.com/pkg/errors"
    14  	"golang.org/x/sys/unix"
    15  )
    16  
    17  type lockfile struct {
    18  	// rwMutex serializes concurrent reader-writer acquisitions in the same process space
    19  	rwMutex *sync.RWMutex
    20  	// stateMutex is used to synchronize concurrent accesses to the state below
    21  	stateMutex *sync.Mutex
    22  	counter    int64
    23  	file       string
    24  	fd         uintptr
    25  	lw         string
    26  	locktype   int16
    27  	locked     bool
    28  	ro         bool
    29  	recursive  bool
    30  }
    31  
    32  // openLock opens the file at path and returns the corresponding file
    33  // descriptor.  Note that the path is opened read-only when ro is set.  If ro
    34  // is unset, openLock will open the path read-write and create the file if
    35  // necessary.
    36  func openLock(path string, ro bool) (int, error) {
    37  	if ro {
    38  		return unix.Open(path, os.O_RDONLY, 0)
    39  	}
    40  	return unix.Open(path, os.O_RDWR|os.O_CREATE, unix.S_IRUSR|unix.S_IWUSR)
    41  }
    42  
    43  // createLockerForPath returns a Locker object, possibly (depending on the platform)
    44  // working inter-process and associated with the specified path.
    45  //
    46  // This function will be called at most once for each path value within a single process.
    47  //
    48  // If ro, the lock is a read-write lock and the returned Locker should correspond to the
    49  // “lock for reading” (shared) operation; otherwise, the lock is either an exclusive lock,
    50  // or a read-write lock and Locker should correspond to the “lock for writing” (exclusive) operation.
    51  //
    52  // WARNING:
    53  // - The lock may or MAY NOT be inter-process.
    54  // - There may or MAY NOT be an actual object on the filesystem created for the specified path.
    55  // - Even if ro, the lock MAY be exclusive.
    56  func createLockerForPath(path string, ro bool) (Locker, error) {
    57  	// Check if we can open the lock.
    58  	fd, err := openLock(path, ro)
    59  	if err != nil {
    60  		return nil, errors.Wrapf(err, "error opening %q", path)
    61  	}
    62  	unix.Close(fd)
    63  
    64  	locktype := unix.F_WRLCK
    65  	if ro {
    66  		locktype = unix.F_RDLCK
    67  	}
    68  	return &lockfile{
    69  		stateMutex: &sync.Mutex{},
    70  		rwMutex:    &sync.RWMutex{},
    71  		file:       path,
    72  		lw:         stringid.GenerateRandomID(),
    73  		locktype:   int16(locktype),
    74  		locked:     false,
    75  		ro:         ro}, nil
    76  }
    77  
    78  // lock locks the lockfile via FCTNL(2) based on the specified type and
    79  // command.
    80  func (l *lockfile) lock(l_type int16, recursive bool) {
    81  	lk := unix.Flock_t{
    82  		Type:   l_type,
    83  		Whence: int16(os.SEEK_SET),
    84  		Start:  0,
    85  		Len:    0,
    86  	}
    87  	switch l_type {
    88  	case unix.F_RDLCK:
    89  		l.rwMutex.RLock()
    90  	case unix.F_WRLCK:
    91  		if recursive {
    92  			// NOTE: that's okay as recursive is only set in RecursiveLock(), so
    93  			// there's no need to protect against hypothetical RDLCK cases.
    94  			l.rwMutex.RLock()
    95  		} else {
    96  			l.rwMutex.Lock()
    97  		}
    98  	default:
    99  		panic(fmt.Sprintf("attempted to acquire a file lock of unrecognized type %d", l_type))
   100  	}
   101  	l.stateMutex.Lock()
   102  	defer l.stateMutex.Unlock()
   103  	if l.counter == 0 {
   104  		// If we're the first reference on the lock, we need to open the file again.
   105  		fd, err := openLock(l.file, l.ro)
   106  		if err != nil {
   107  			panic(fmt.Sprintf("error opening %q", l.file))
   108  		}
   109  		unix.CloseOnExec(fd)
   110  		l.fd = uintptr(fd)
   111  
   112  		// Optimization: only use the (expensive) fcntl syscall when
   113  		// the counter is 0.  In this case, we're either the first
   114  		// reader lock or a writer lock.
   115  		for unix.FcntlFlock(l.fd, unix.F_SETLKW, &lk) != nil {
   116  			time.Sleep(10 * time.Millisecond)
   117  		}
   118  	}
   119  	l.locktype = l_type
   120  	l.locked = true
   121  	l.recursive = recursive
   122  	l.counter++
   123  }
   124  
   125  // Lock locks the lockfile as a writer.  Note that RLock() will be called if
   126  // the lock is a read-only one.
   127  func (l *lockfile) Lock() {
   128  	if l.ro {
   129  		l.RLock()
   130  	} else {
   131  		l.lock(unix.F_WRLCK, false)
   132  	}
   133  }
   134  
   135  // RecursiveLock locks the lockfile as a writer but allows for recursive
   136  // acquisitions within the same process space.  Note that RLock() will be called
   137  // if it's a lockTypReader lock.
   138  func (l *lockfile) RecursiveLock() {
   139  	if l.ro {
   140  		l.RLock()
   141  	} else {
   142  		l.lock(unix.F_WRLCK, true)
   143  	}
   144  }
   145  
   146  // LockRead locks the lockfile as a reader.
   147  func (l *lockfile) RLock() {
   148  	l.lock(unix.F_RDLCK, false)
   149  }
   150  
   151  // Unlock unlocks the lockfile.
   152  func (l *lockfile) Unlock() {
   153  	lk := unix.Flock_t{
   154  		Type:   unix.F_UNLCK,
   155  		Whence: int16(os.SEEK_SET),
   156  		Start:  0,
   157  		Len:    0,
   158  		Pid:    int32(os.Getpid()),
   159  	}
   160  	l.stateMutex.Lock()
   161  	if l.locked == false {
   162  		// Panic when unlocking an unlocked lock.  That's a violation
   163  		// of the lock semantics and will reveal such.
   164  		panic("calling Unlock on unlocked lock")
   165  	}
   166  	l.counter--
   167  	if l.counter < 0 {
   168  		// Panic when the counter is negative.  There is no way we can
   169  		// recover from a corrupted lock and we need to protect the
   170  		// storage from corruption.
   171  		panic(fmt.Sprintf("lock %q has been unlocked too often", l.file))
   172  	}
   173  	if l.counter == 0 {
   174  		// We should only release the lock when the counter is 0 to
   175  		// avoid releasing read-locks too early; a given process may
   176  		// acquire a read lock multiple times.
   177  		l.locked = false
   178  		for unix.FcntlFlock(l.fd, unix.F_SETLKW, &lk) != nil {
   179  			time.Sleep(10 * time.Millisecond)
   180  		}
   181  		// Close the file descriptor on the last unlock.
   182  		unix.Close(int(l.fd))
   183  	}
   184  	if l.locktype == unix.F_RDLCK || l.recursive {
   185  		l.rwMutex.RUnlock()
   186  	} else {
   187  		l.rwMutex.Unlock()
   188  	}
   189  	l.stateMutex.Unlock()
   190  }
   191  
   192  // Locked checks if lockfile is locked for writing by a thread in this process.
   193  func (l *lockfile) Locked() bool {
   194  	l.stateMutex.Lock()
   195  	defer l.stateMutex.Unlock()
   196  	return l.locked && (l.locktype == unix.F_WRLCK)
   197  }
   198  
   199  // Touch updates the lock file with the UID of the user.
   200  func (l *lockfile) Touch() error {
   201  	l.stateMutex.Lock()
   202  	if !l.locked || (l.locktype != unix.F_WRLCK) {
   203  		panic("attempted to update last-writer in lockfile without the write lock")
   204  	}
   205  	l.stateMutex.Unlock()
   206  	l.lw = stringid.GenerateRandomID()
   207  	id := []byte(l.lw)
   208  	_, err := unix.Seek(int(l.fd), 0, os.SEEK_SET)
   209  	if err != nil {
   210  		return err
   211  	}
   212  	n, err := unix.Write(int(l.fd), id)
   213  	if err != nil {
   214  		return err
   215  	}
   216  	if n != len(id) {
   217  		return unix.ENOSPC
   218  	}
   219  	err = unix.Fsync(int(l.fd))
   220  	if err != nil {
   221  		return err
   222  	}
   223  	return nil
   224  }
   225  
   226  // Modified indicates if the lockfile has been updated since the last time it
   227  // was loaded.
   228  func (l *lockfile) Modified() (bool, error) {
   229  	id := []byte(l.lw)
   230  	l.stateMutex.Lock()
   231  	if !l.locked {
   232  		panic("attempted to check last-writer in lockfile without locking it first")
   233  	}
   234  	l.stateMutex.Unlock()
   235  	_, err := unix.Seek(int(l.fd), 0, os.SEEK_SET)
   236  	if err != nil {
   237  		return true, err
   238  	}
   239  	n, err := unix.Read(int(l.fd), id)
   240  	if err != nil {
   241  		return true, err
   242  	}
   243  	if n != len(id) {
   244  		return true, nil
   245  	}
   246  	lw := l.lw
   247  	l.lw = string(id)
   248  	return l.lw != lw, nil
   249  }
   250  
   251  // IsReadWriteLock indicates if the lock file is a read-write lock.
   252  func (l *lockfile) IsReadWrite() bool {
   253  	return !l.ro
   254  }
   255  
   256  // TouchedSince indicates if the lock file has been touched since the specified time
   257  func (l *lockfile) TouchedSince(when time.Time) bool {
   258  	st, err := system.Fstat(int(l.fd))
   259  	if err != nil {
   260  		return true
   261  	}
   262  	mtim := st.Mtim()
   263  	touched := time.Unix(mtim.Unix())
   264  	return when.Before(touched)
   265  }