From a63d0201d443e9061aa97879f4e30bb188786b8e Mon Sep 17 00:00:00 2001 From: Fazlul Shahriar Date: Sat, 25 Jul 2020 23:30:46 -0400 Subject: [PATCH] Fix build on Plan 9 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. --- fslock.go | 6 +---- fslock_plan9.go | 33 +++++++++++++++++++++++++ fslock_posix.go | 12 +++++++++ fslock_test.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 fslock_plan9.go create mode 100644 fslock_posix.go diff --git a/fslock.go b/fslock.go index 7dc0925..ad519e0 100644 --- a/fslock.go +++ b/fslock.go @@ -6,7 +6,6 @@ import ( "os" "path/filepath" "strings" - "syscall" util "github.com/ipfs/go-ipfs-util" logging "github.com/ipfs/go-log" @@ -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, diff --git a/fslock_plan9.go b/fslock_plan9.go new file mode 100644 index 0000000..30365fc --- /dev/null +++ b/fslock_plan9.go @@ -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) +} diff --git a/fslock_posix.go b/fslock_posix.go new file mode 100644 index 0000000..c24e1e1 --- /dev/null +++ b/fslock_posix.go @@ -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") +} diff --git a/fslock_test.go b/fslock_test.go index 1d9acf1..cd2ec90 100644 --- a/fslock_test.go +++ b/fslock_test.go @@ -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" ) @@ -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) + } +}