github.com/sercand/please@v13.4.0+incompatible/src/core/lock.go (about) 1 // Contains utility functions for managing an exclusive lock file. 2 // Based on flock() underneath so 3 4 package core 5 6 import ( 7 "io" 8 "io/ioutil" 9 "os" 10 "path" 11 "strings" 12 "syscall" 13 14 "github.com/pkg/xattr" 15 ) 16 17 const lockFilePath = "plz-out/.lock" 18 19 var lockFile *os.File 20 21 // AcquireRepoLock opens the lock file and acquires the lock. 22 // Dies if the lock cannot be successfully acquired. 23 func AcquireRepoLock() { 24 var err error 25 // There is of course technically a bit of a race condition between the file & flock operations here, 26 // but it shouldn't matter much since we're trying to mutually exclude plz processes started by the user 27 // which (one hopes) they wouldn't normally do simultaneously. 28 os.MkdirAll(path.Dir(lockFilePath), DirPermissions) 29 // TODO(pebers): This doesn't seem quite as intended, I think the file still gets truncated sometimes. 30 // Not sure why since I'm not passing O_TRUNC... 31 if lockFile, err = os.OpenFile(lockFilePath, os.O_RDWR|os.O_CREATE, 0644); err != nil && !os.IsNotExist(err) { 32 log.Fatalf("Failed to acquire lock: %s", err) 33 } else if lockFile, err = os.Create(lockFilePath); err != nil { 34 log.Fatalf("Failed to create lock: %s", err) 35 } 36 // Try a non-blocking acquire first so we can warn the user if we're waiting. 37 log.Debug("Attempting to acquire lock %s...", lockFilePath) 38 if err := syscall.Flock(int(lockFile.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err == nil { 39 log.Debug("Acquired lock %s", lockFilePath) 40 } else { 41 log.Warning("Looks like another plz is already running in this repo. Waiting for it to finish...") 42 if err := syscall.Flock(int(lockFile.Fd()), syscall.LOCK_EX); err != nil { 43 log.Fatalf("Failed to acquire lock: %s", err) 44 } 45 } 46 47 // Record the operation performed. 48 if _, err = lockFile.Seek(0, io.SeekStart); err == nil { 49 if n, err := lockFile.Write([]byte(strings.Join(os.Args[1:], " ") + "\n")); err == nil { 50 lockFile.Truncate(int64(n)) 51 } 52 } 53 54 // Quick test of xattrs; everything will fail later if we can't write them, so make 55 // sure plz-out supports them now and give a clear error if not. 56 if err := xattr.Set(lockFilePath, "user.plz_build", []byte("lock")); err != nil { 57 log.Fatalf("Failed to set xattrs:%s\nMaybe plz-out is on a filesystem that doesn't support them?", err) 58 } 59 } 60 61 // ReleaseRepoLock releases the lock and closes the file handle. 62 // Does not die on errors, at this point it wouldn't really do any good. 63 func ReleaseRepoLock() { 64 if lockFile == nil { 65 log.Errorf("Lock file not acquired!") 66 return 67 } 68 if err := syscall.Flock(int(lockFile.Fd()), syscall.LOCK_UN); err != nil { 69 log.Errorf("Failed to release lock: %s", err) // No point making this fatal really 70 } 71 if err := lockFile.Close(); err != nil { 72 log.Errorf("Failed to close lock file: %s", err) 73 } 74 } 75 76 // ReadLastOperationOrDie reads the last operation performed from the lock file. Dies if unsuccessful. 77 func ReadLastOperationOrDie() []string { 78 contents, err := ioutil.ReadFile(lockFilePath) 79 if err != nil || len(contents) == 0 { 80 log.Fatalf("Sorry OP, can't read previous operation :(") 81 } 82 return strings.Split(strings.TrimSpace(string(contents)), " ") 83 }