diff --git a/filelock/LICENSE b/filelock/LICENSE new file mode 100644 index 0000000..fec05ce --- /dev/null +++ b/filelock/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2011 The LevelDB-Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/filelock/file_lock_darwin_amd64.go b/filelock/file_lock_darwin_amd64.go new file mode 100644 index 0000000..efc12fa --- /dev/null +++ b/filelock/file_lock_darwin_amd64.go @@ -0,0 +1,51 @@ +// Copyright 2012 The LevelDB-Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package filelock + +import ( + "io" + "os" + "syscall" + "unsafe" +) + +// lockCloser hides all of an os.File's methods, except for Close. +type lockCloser struct { + f *os.File +} + +func (l lockCloser) Close() error { + return l.f.Close() +} + +func Lock(name string) (io.Closer, error) { + f, err := os.Create(name) + if err != nil { + return nil, err + } + + // This type matches C's "struct flock" defined in /usr/include/sys/fcntl.h. + // TODO: move this into the standard syscall package. + k := struct { + Start uint64 // sizeof(off_t): 8 + Len uint64 // sizeof(off_t): 8 + Pid uint32 // sizeof(pid_t): 4 + Type uint16 // sizeof(short): 2 + Whence uint16 // sizeof(short): 2 + }{ + Type: syscall.F_WRLCK, + Whence: uint16(os.SEEK_SET), + Start: 0, + Len: 0, // 0 means to lock the entire file. + Pid: uint32(os.Getpid()), + } + + _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, f.Fd(), uintptr(syscall.F_SETLK), uintptr(unsafe.Pointer(&k))) + if errno != 0 { + f.Close() + return nil, errno + } + return lockCloser{f}, nil +} diff --git a/filelock/file_lock_freebsd.go b/filelock/file_lock_freebsd.go new file mode 100644 index 0000000..87e7630 --- /dev/null +++ b/filelock/file_lock_freebsd.go @@ -0,0 +1,53 @@ +// Copyright 2014 The LevelDB-Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package filelock + +import ( + "io" + "os" + "syscall" + "unsafe" +) + +// lockCloser hides all of an os.File's methods, except for Close. +type lockCloser struct { + f *os.File +} + +func (l lockCloser) Close() error { + return l.f.Close() +} + +func Lock(name string) (io.Closer, error) { + f, err := os.Create(name) + if err != nil { + return nil, err + } + + // This type matches C's "struct flock" defined in /usr/include/fcntl.h. + // TODO: move this into the standard syscall package. + k := struct { + Start int64 /* off_t starting offset */ + Len int64 /* off_t len = 0 means until end of file */ + Pid int32 /* pid_t lock owner */ + Type int16 /* short lock type: read/write, etc. */ + Whence int16 /* short type of l_start */ + Sysid int32 /* int remote system id or zero for local */ + }{ + Start: 0, + Len: 0, // 0 means to lock the entire file. + Pid: int32(os.Getpid()), + Type: syscall.F_WRLCK, + Whence: int16(os.SEEK_SET), + Sysid: 0, + } + + _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, f.Fd(), uintptr(syscall.F_SETLK), uintptr(unsafe.Pointer(&k))) + if errno != 0 { + f.Close() + return nil, errno + } + return lockCloser{f}, nil +} diff --git a/filelock/file_lock_generic.go b/filelock/file_lock_generic.go new file mode 100644 index 0000000..2415e45 --- /dev/null +++ b/filelock/file_lock_generic.go @@ -0,0 +1,21 @@ +// Copyright 2012 The LevelDB-Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !linux,!amd64 +// +build !darwin,!amd64 +// +build !openbsd,!amd64 +// +build !freebsd +// +build !windows + +package filelock + +import ( + "fmt" + "io" + "runtime" +) + +func Lock(name string) (io.Closer, error) { + return nil, fmt.Errorf("leveldb/db: file locking is not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} diff --git a/filelock/file_lock_linux_amd64.go b/filelock/file_lock_linux_amd64.go new file mode 100644 index 0000000..7fd42ef --- /dev/null +++ b/filelock/file_lock_linux_amd64.go @@ -0,0 +1,51 @@ +// Copyright 2012 The LevelDB-Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package filelock + +import ( + "io" + "os" + "syscall" + "unsafe" +) + +// lockCloser hides all of an os.File's methods, except for Close. +type lockCloser struct { + f *os.File +} + +func (l lockCloser) Close() error { + return l.f.Close() +} + +func Lock(name string) (io.Closer, error) { + f, err := os.Create(name) + if err != nil { + return nil, err + } + + // This type matches C's "struct flock" defined in /usr/include/bits/fcntl.h. + // TODO: move this into the standard syscall package. + k := struct { + Type uint32 + Whence uint32 + Start uint64 + Len uint64 + Pid uint32 + }{ + Type: syscall.F_WRLCK, + Whence: uint32(os.SEEK_SET), + Start: 0, + Len: 0, // 0 means to lock the entire file. + Pid: uint32(os.Getpid()), + } + + _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, f.Fd(), uintptr(syscall.F_SETLK), uintptr(unsafe.Pointer(&k))) + if errno != 0 { + f.Close() + return nil, errno + } + return lockCloser{f}, nil +} diff --git a/filelock/file_lock_openbsd_amd64.go b/filelock/file_lock_openbsd_amd64.go new file mode 100644 index 0000000..0e89f51 --- /dev/null +++ b/filelock/file_lock_openbsd_amd64.go @@ -0,0 +1,51 @@ +// Copyright 2013 The LevelDB-Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package filelock + +import ( + "io" + "os" + "syscall" + "unsafe" +) + +// lockCloser hides all of an os.File's methods, except for Close. +type lockCloser struct { + f *os.File +} + +func (l lockCloser) Close() error { + return l.f.Close() +} + +func Lock(name string) (io.Closer, error) { + f, err := os.Create(name) + if err != nil { + return nil, err + } + + // This type matches C's "struct flock" defined in /usr/include/fcntl.h. + // TODO: move this into the standard syscall package. + k := struct { + Start uint64 + Len uint64 + Pid uint32 + Type uint32 + Whence uint32 + }{ + Type: syscall.F_WRLCK, + Whence: uint32(os.SEEK_SET), + Start: 0, + Len: 0, // 0 means to lock the entire file. + Pid: uint32(os.Getpid()), + } + + _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, f.Fd(), uintptr(syscall.F_SETLK), uintptr(unsafe.Pointer(&k))) + if errno != 0 { + f.Close() + return nil, errno + } + return lockCloser{f}, nil +} diff --git a/filelock/file_lock_test.go b/filelock/file_lock_test.go new file mode 100644 index 0000000..3575fc2 --- /dev/null +++ b/filelock/file_lock_test.go @@ -0,0 +1,77 @@ +// Copyright 2014 The LevelDB-Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package filelock + +import ( + "bytes" + "flag" + "io/ioutil" + "os" + "os/exec" + "testing" +) + +var lockFilename = flag.String("lockfile", "", "File to lock. Non-empty value pimples child process.") + +func spawn(prog, filename string) ([]byte, error) { + return exec.Command(prog, "-lockfile", filename, "-test.v", + "-test.run=TestLock$").CombinedOutput() +} + +// TestLock locks a file, spawns a second process that attempts to grab the +// lock to verify it fails. +// Then it closes the lock, and spawns a third copy to verify it can be +// relocked. +func TestLock(t *testing.T) { + child := *lockFilename != "" + var filename string + if child { + filename = *lockFilename + } else { + f, err := ioutil.TempFile("", "") + if err != nil { + t.Fatal(err) + } + filename = f.Name() + defer os.Remove(filename) + } + + // Avoid truncating an existing, non-empty file. + fi, err := os.Stat(filename) + if err == nil && fi.Size() != 0 { + t.Fatal("The file %s is not empty", filename) + } + + t.Logf("Locking %s\n", filename) + lock, err := Lock(filename) + if err != nil { + t.Fatalf("Could not lock %s: %v", filename, err) + } + + if !child { + t.Logf("Spawning child, should fail to grab lock.") + out, err := spawn(os.Args[0], filename) + if err == nil { + t.Fatalf("Attempt to grab open lock should have failed.\n%s", out) + } + if !bytes.Contains(out, []byte("Could not lock")) { + t.Fatalf("Child failed with unexpected output: %s\n", out) + } + t.Logf("Child failed to grab lock as expected.") + } + + t.Logf("Unlocking %s", filename) + if err := lock.Close(); err != nil { + t.Fatalf("Could not unlock %s: %v", filename, err) + } + + if !child { + t.Logf("Spawning child, should successfully grab lock.") + if out, err := spawn(os.Args[0], filename); err != nil { + t.Fatalf("Attempt to re-open lock should have succeeded: %v\n%s", + err, out) + } + t.Logf("Child grabbed lock.") + } +} diff --git a/filelock/file_lock_windows.go b/filelock/file_lock_windows.go new file mode 100644 index 0000000..5d3e4ba --- /dev/null +++ b/filelock/file_lock_windows.go @@ -0,0 +1,36 @@ +// Copyright 2013 The LevelDB-Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package filelock + +import ( + "io" + "syscall" +) + +// lockCloser hides all of an syscall.Handle's methods, except for Close. +type lockCloser struct { + fd syscall.Handle +} + +func (l lockCloser) Close() error { + return syscall.Close(l.fd) +} + +func Lock(name string) (io.Closer, error) { + p, err := syscall.UTF16PtrFromString(name) + if err != nil { + return nil, err + } + fd, err := syscall.CreateFile(p, + syscall.GENERIC_READ|syscall.GENERIC_WRITE, + 0, nil, syscall.CREATE_ALWAYS, + syscall.FILE_ATTRIBUTE_NORMAL, + 0, + ) + if err != nil { + return nil, err + } + return lockCloser{fd: fd}, nil +}