Skip to content

Commit

Permalink
Fix build on Plan 9
Browse files Browse the repository at this point in the history
Plan 9 doesn't have syscall.EAGAIN. Copy some code from Go's internal
locakedfile package to check whether the file is locked by another
process.
  • Loading branch information
fhs committed Jul 26, 2020
1 parent 14b474c commit a63d020
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 5 deletions.
6 changes: 1 addition & 5 deletions fslock.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"os"
"path/filepath"
"strings"
"syscall"

util "github.com/ipfs/go-ipfs-util"
logging "github.com/ipfs/go-log"
Expand All @@ -30,10 +29,7 @@ func Lock(confdir, lockFileName string) (io.Closer, error) {
lk, err := lock.Lock(lockFilePath)
if err != nil {
switch {
case err == syscall.EAGAIN:
// EAGAIN == someone else has the lock
fallthrough
case strings.Contains(err.Error(), "resource temporarily unavailable"):
case lockedByOthers(err):
return lk, &os.PathError{
Op: "lock",
Path: lockFilePath,
Expand Down
33 changes: 33 additions & 0 deletions fslock_plan9.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package fslock

import "strings"

// Opening an exclusive-use file returns an error.
// The expected error strings are:
//
// - "open/create -- file is locked" (cwfs, kfs)
// - "exclusive lock" (fossil)
// - "exclusive use file already open" (ramfs)
//
// See https://github.com/golang/go/blob/go1.15rc1/src/cmd/go/internal/lockedfile/lockedfile_plan9.go#L16
var lockedErrStrings = [...]string{
"file is locked",
"exclusive lock",
"exclusive use file already open",
}

// isLockedPlan9 return whether an os.OpenFile error indicates that
// a file with the ModeExclusive bit set is already open.
func isLockedPlan9(s string) bool {
for _, frag := range lockedErrStrings {
if strings.Contains(s, frag) {
return true
}
}
return false
}

func lockedByOthers(err error) bool {
s := err.Error()
return strings.Contains(s, "Lock Create of") && isLockedPlan9(s)
}
12 changes: 12 additions & 0 deletions fslock_posix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// +build !plan9

package fslock

import (
"strings"
"syscall"
)

func lockedByOthers(err error) bool {
return err == syscall.EAGAIN || strings.Contains(err.Error(), "resource temporarily unavailable")
}
65 changes: 65 additions & 0 deletions fslock_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package fslock_test

import (
"bufio"
"io/ioutil"
"os"
"os/exec"
"path"
"strings"
"testing"
"time"

lock "github.com/ipfs/go-fs-lock"
)
Expand Down Expand Up @@ -94,3 +99,63 @@ func TestLockMultiple(t *testing.T) {
assertLock(t, confdir, lockFile1, false)
assertLock(t, confdir, lockFile2, false)
}

func TestLockedByOthers(t *testing.T) {
const (
lockedMsg = "locked\n"
lockFile = "my-test.lock"
wantErr = "someone else has the lock"
)

if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { // child process
confdir := os.Args[3]
if _, err := lock.Lock(confdir, lockFile); err != nil {
t.Fatalf("child lock: %v", err)
}
os.Stdout.WriteString(lockedMsg)
time.Sleep(10 * time.Minute)
return
}

confdir, err := ioutil.TempDir("", "go-fs-lock-test")
if err != nil {
t.Fatalf("creating temporary directory: %v", err)
}
defer os.RemoveAll(confdir)

// Execute a child process that locks the file.
cmd := exec.Command(os.Args[0], "-test.run=TestLockedByOthers", "--", confdir)
cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
cmd.Stderr = os.Stderr
stdout, err := cmd.StdoutPipe()
if err != nil {
t.Fatalf("cmd.StdoutPipe: %v", err)
}
if err = cmd.Start(); err != nil {
t.Fatalf("cmd.Start: %v", err)
}
defer cmd.Process.Kill()

// Wait for the child to lock the file.
b := bufio.NewReader(stdout)
line, err := b.ReadString('\n')
if err != nil {
t.Fatalf("read from child: %v", err)
}
if got, want := line, lockedMsg; got != want {
t.Fatalf("got %q from child; want %q", got, want)
}

// Parent should not be able to lock the file.
_, err = lock.Lock(confdir, lockFile)
if err == nil {
t.Fatalf("parent successfully acquired the lock")
}
pe, ok := err.(*os.PathError)
if !ok {
t.Fatalf("wrong error type %T", err)
}
if got := pe.Error(); !strings.Contains(got, wantErr) {
t.Fatalf("error %q does not contain %q", got, wantErr)
}
}

0 comments on commit a63d020

Please sign in to comment.