mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-10-31 08:03:43 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			196 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			196 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2015 Tim Heckman. All rights reserved.
 | |
| // Use of this source code is governed by the BSD 3-Clause
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| // +build !windows
 | |
| 
 | |
| package flock
 | |
| 
 | |
| import (
 | |
| 	"os"
 | |
| 	"syscall"
 | |
| )
 | |
| 
 | |
| // Lock is a blocking call to try and take an exclusive file lock. It will wait
 | |
| // until it is able to obtain the exclusive file lock. It's recommended that
 | |
| // TryLock() be used over this function. This function may block the ability to
 | |
| // query the current Locked() or RLocked() status due to a RW-mutex lock.
 | |
| //
 | |
| // If we are already exclusive-locked, this function short-circuits and returns
 | |
| // immediately assuming it can take the mutex lock.
 | |
| //
 | |
| // If the *Flock has a shared lock (RLock), this may transparently replace the
 | |
| // shared lock with an exclusive lock on some UNIX-like operating systems. Be
 | |
| // careful when using exclusive locks in conjunction with shared locks
 | |
| // (RLock()), because calling Unlock() may accidentally release the exclusive
 | |
| // lock that was once a shared lock.
 | |
| func (f *Flock) Lock() error {
 | |
| 	return f.lock(&f.l, syscall.LOCK_EX)
 | |
| }
 | |
| 
 | |
| // RLock is a blocking call to try and take a shared file lock. It will wait
 | |
| // until it is able to obtain the shared file lock. It's recommended that
 | |
| // TryRLock() be used over this function. This function may block the ability to
 | |
| // query the current Locked() or RLocked() status due to a RW-mutex lock.
 | |
| //
 | |
| // If we are already shared-locked, this function short-circuits and returns
 | |
| // immediately assuming it can take the mutex lock.
 | |
| func (f *Flock) RLock() error {
 | |
| 	return f.lock(&f.r, syscall.LOCK_SH)
 | |
| }
 | |
| 
 | |
| func (f *Flock) lock(locked *bool, flag int) error {
 | |
| 	f.m.Lock()
 | |
| 	defer f.m.Unlock()
 | |
| 
 | |
| 	if *locked {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if f.fh == nil {
 | |
| 		if err := f.setFh(); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if err := syscall.Flock(int(f.fh.Fd()), flag); err != nil {
 | |
| 		shouldRetry, reopenErr := f.reopenFDOnError(err)
 | |
| 		if reopenErr != nil {
 | |
| 			return reopenErr
 | |
| 		}
 | |
| 
 | |
| 		if !shouldRetry {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		if err = syscall.Flock(int(f.fh.Fd()), flag); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	*locked = true
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Unlock is a function to unlock the file. This file takes a RW-mutex lock, so
 | |
| // while it is running the Locked() and RLocked() functions will be blocked.
 | |
| //
 | |
| // This function short-circuits if we are unlocked already. If not, it calls
 | |
| // syscall.LOCK_UN on the file and closes the file descriptor. It does not
 | |
| // remove the file from disk. It's up to your application to do.
 | |
| //
 | |
| // Please note, if your shared lock became an exclusive lock this may
 | |
| // unintentionally drop the exclusive lock if called by the consumer that
 | |
| // believes they have a shared lock. Please see Lock() for more details.
 | |
| func (f *Flock) Unlock() error {
 | |
| 	f.m.Lock()
 | |
| 	defer f.m.Unlock()
 | |
| 
 | |
| 	// if we aren't locked or if the lockfile instance is nil
 | |
| 	// just return a nil error because we are unlocked
 | |
| 	if (!f.l && !f.r) || f.fh == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// mark the file as unlocked
 | |
| 	if err := syscall.Flock(int(f.fh.Fd()), syscall.LOCK_UN); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	f.fh.Close()
 | |
| 
 | |
| 	f.l = false
 | |
| 	f.r = false
 | |
| 	f.fh = nil
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // TryLock is the preferred function for taking an exclusive file lock. This
 | |
| // function takes an RW-mutex lock before it tries to lock the file, so there is
 | |
| // the possibility that this function may block for a short time if another
 | |
| // goroutine is trying to take any action.
 | |
| //
 | |
| // The actual file lock is non-blocking. If we are unable to get the exclusive
 | |
| // file lock, the function will return false instead of waiting for the lock. If
 | |
| // we get the lock, we also set the *Flock instance as being exclusive-locked.
 | |
| func (f *Flock) TryLock() (bool, error) {
 | |
| 	return f.try(&f.l, syscall.LOCK_EX)
 | |
| }
 | |
| 
 | |
| // TryRLock is the preferred function for taking a shared file lock. This
 | |
| // function takes an RW-mutex lock before it tries to lock the file, so there is
 | |
| // the possibility that this function may block for a short time if another
 | |
| // goroutine is trying to take any action.
 | |
| //
 | |
| // The actual file lock is non-blocking. If we are unable to get the shared file
 | |
| // lock, the function will return false instead of waiting for the lock. If we
 | |
| // get the lock, we also set the *Flock instance as being share-locked.
 | |
| func (f *Flock) TryRLock() (bool, error) {
 | |
| 	return f.try(&f.r, syscall.LOCK_SH)
 | |
| }
 | |
| 
 | |
| func (f *Flock) try(locked *bool, flag int) (bool, error) {
 | |
| 	f.m.Lock()
 | |
| 	defer f.m.Unlock()
 | |
| 
 | |
| 	if *locked {
 | |
| 		return true, nil
 | |
| 	}
 | |
| 
 | |
| 	if f.fh == nil {
 | |
| 		if err := f.setFh(); err != nil {
 | |
| 			return false, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	var retried bool
 | |
| retry:
 | |
| 	err := syscall.Flock(int(f.fh.Fd()), flag|syscall.LOCK_NB)
 | |
| 
 | |
| 	switch err {
 | |
| 	case syscall.EWOULDBLOCK:
 | |
| 		return false, nil
 | |
| 	case nil:
 | |
| 		*locked = true
 | |
| 		return true, nil
 | |
| 	}
 | |
| 	if !retried {
 | |
| 		if shouldRetry, reopenErr := f.reopenFDOnError(err); reopenErr != nil {
 | |
| 			return false, reopenErr
 | |
| 		} else if shouldRetry {
 | |
| 			retried = true
 | |
| 			goto retry
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return false, err
 | |
| }
 | |
| 
 | |
| // reopenFDOnError determines whether we should reopen the file handle
 | |
| // in readwrite mode and try again. This comes from util-linux/sys-utils/flock.c:
 | |
| //  Since Linux 3.4 (commit 55725513)
 | |
| //  Probably NFSv4 where flock() is emulated by fcntl().
 | |
| func (f *Flock) reopenFDOnError(err error) (bool, error) {
 | |
| 	if err != syscall.EIO && err != syscall.EBADF {
 | |
| 		return false, nil
 | |
| 	}
 | |
| 	if st, err := f.fh.Stat(); err == nil {
 | |
| 		// if the file is able to be read and written
 | |
| 		if st.Mode()&0600 == 0600 {
 | |
| 			f.fh.Close()
 | |
| 			f.fh = nil
 | |
| 
 | |
| 			// reopen in read-write mode and set the filehandle
 | |
| 			fh, err := os.OpenFile(f.path, os.O_CREATE|os.O_RDWR, os.FileMode(0600))
 | |
| 			if err != nil {
 | |
| 				return false, err
 | |
| 			}
 | |
| 			f.fh = fh
 | |
| 			return true, nil
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return false, nil
 | |
| }
 | 
