github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/osutil/flock.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package osutil 21 22 import ( 23 "errors" 24 "os" 25 "syscall" 26 ) 27 28 // FileLock describes a file system lock 29 type FileLock struct { 30 file *os.File 31 } 32 33 var ErrAlreadyLocked = errors.New("cannot acquire lock, already locked") 34 35 // OpenExistingLockForReading opens an existing lock file given by "path". 36 // The lock is opened in read-only mode. 37 func OpenExistingLockForReading(path string) (*FileLock, error) { 38 flag := syscall.O_RDONLY | syscall.O_NOFOLLOW | syscall.O_CLOEXEC 39 file, err := os.OpenFile(path, flag, 0) 40 if err != nil { 41 return nil, err 42 } 43 l := &FileLock{file: file} 44 return l, nil 45 } 46 47 // NewFileLockWithMode creates and opens the lock file given by "path" with the given mode. 48 func NewFileLockWithMode(path string, mode os.FileMode) (*FileLock, error) { 49 flag := syscall.O_RDWR | syscall.O_CREAT | syscall.O_NOFOLLOW | syscall.O_CLOEXEC 50 file, err := os.OpenFile(path, flag, mode) 51 if err != nil { 52 return nil, err 53 } 54 l := &FileLock{file: file} 55 return l, nil 56 } 57 58 // NewFileLock creates and opens the lock file given by "path" with mode 0600. 59 func NewFileLock(path string) (*FileLock, error) { 60 return NewFileLockWithMode(path, 0600) 61 } 62 63 // Path returns the path of the lock file. 64 func (l *FileLock) Path() string { 65 return l.file.Name() 66 } 67 68 // File returns the underlying file. 69 func (l *FileLock) File() *os.File { 70 return l.file 71 } 72 73 // Close closes the lock, unlocking it automatically if needed. 74 func (l *FileLock) Close() error { 75 return l.file.Close() 76 } 77 78 // Lock acquires an exclusive lock and blocks until the lock is free. 79 // 80 // Only one process can acquire an exclusive lock at a given time, preventing 81 // shared or exclusive locks from being acquired. 82 func (l *FileLock) Lock() error { 83 return syscall.Flock(int(l.file.Fd()), syscall.LOCK_EX) 84 } 85 86 // Lock acquires an shared lock and blocks until the lock is free. 87 // 88 // Multiple processes can acquire a shared lock at the same time, unless an 89 // exclusive lock is held. 90 func (l *FileLock) ReadLock() error { 91 return syscall.Flock(int(l.file.Fd()), syscall.LOCK_SH) 92 } 93 94 // TryLock acquires an exclusive lock and errors if the lock cannot be acquired. 95 func (l *FileLock) TryLock() error { 96 err := syscall.Flock(int(l.file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) 97 if err == syscall.EWOULDBLOCK { 98 return ErrAlreadyLocked 99 } 100 return err 101 } 102 103 // Unlock releases an acquired lock. 104 func (l *FileLock) Unlock() error { 105 return syscall.Flock(int(l.file.Fd()), syscall.LOCK_UN) 106 }