1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build plan9 6 7 package lockedfile 8 9 import ( 10 "io/fs" 11 "math/rand" 12 "os" 13 "strings" 14 "time" 15 ) 16 17 // Opening an exclusive-use file returns an error. 18 // The expected error strings are: 19 // 20 // - "open/create -- file is locked" (cwfs, kfs) 21 // - "exclusive lock" (fossil) 22 // - "exclusive use file already open" (ramfs) 23 var lockedErrStrings = [...]string{ 24 "file is locked", 25 "exclusive lock", 26 "exclusive use file already open", 27 } 28 29 // Even though plan9 doesn't support the Lock/RLock/Unlock functions to 30 // manipulate already-open files, IsLocked is still meaningful: os.OpenFile 31 // itself may return errors that indicate that a file with the ModeExclusive bit 32 // set is already open. 33 func isLocked(err error) bool { 34 s := err.Error() 35 36 for _, frag := range lockedErrStrings { 37 if strings.Contains(s, frag) { 38 return true 39 } 40 } 41 42 return false 43 } 44 45 func openFile(name string, flag int, perm fs.FileMode) (*os.File, error) { 46 // Plan 9 uses a mode bit instead of explicit lock/unlock syscalls. 47 // 48 // Per http://man.cat-v.org/plan_9/5/stat: “Exclusive use files may be open 49 // for I/O by only one fid at a time across all clients of the server. If a 50 // second open is attempted, it draws an error.” 51 // 52 // So we can try to open a locked file, but if it fails we're on our own to 53 // figure out when it becomes available. We'll use exponential backoff with 54 // some jitter and an arbitrary limit of 500ms. 55 56 // If the file was unpacked or created by some other program, it might not 57 // have the ModeExclusive bit set. Set it before we call OpenFile, so that we 58 // can be confident that a successful OpenFile implies exclusive use. 59 if fi, err := os.Stat(name); err == nil { 60 if fi.Mode()&fs.ModeExclusive == 0 { 61 if err := os.Chmod(name, fi.Mode()|fs.ModeExclusive); err != nil { 62 return nil, err 63 } 64 } 65 } else if !os.IsNotExist(err) { 66 return nil, err 67 } 68 69 nextSleep := 1 * time.Millisecond 70 const maxSleep = 500 * time.Millisecond 71 for { 72 f, err := os.OpenFile(name, flag, perm|fs.ModeExclusive) 73 if err == nil { 74 return f, nil 75 } 76 77 if !isLocked(err) { 78 return nil, err 79 } 80 81 time.Sleep(nextSleep) 82 83 nextSleep += nextSleep 84 if nextSleep > maxSleep { 85 nextSleep = maxSleep 86 } 87 // Apply 10% jitter to avoid synchronizing collisions. 88 nextSleep += time.Duration((0.1*rand.Float64() - 0.05) * float64(nextSleep)) 89 } 90 } 91 92 func closeFile(f *os.File) error { 93 return f.Close() 94 } 95