diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index bcc07be29..4951496c9 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -6,6 +6,10 @@ "./..." ], "Deps": [ + { + "ImportPath": "github.com/alexflint/go-filemutex", + "Rev": "72bdc8eae2aef913234599b837f5dda445ca9bd9" + }, { "ImportPath": "github.com/containernetworking/cni/libcni", "Comment": "v0.6.0-rc1", diff --git a/plugins/ipam/host-local/backend/disk/backend.go b/plugins/ipam/host-local/backend/disk/backend.go index 0f5a5f534..08bb4eb97 100644 --- a/plugins/ipam/host-local/backend/disk/backend.go +++ b/plugins/ipam/host-local/backend/disk/backend.go @@ -22,6 +22,7 @@ import ( "strings" "github.com/containernetworking/plugins/plugins/ipam/host-local/backend" + "runtime" ) const lastIPFilePrefix = "last_reserved_ip." @@ -55,7 +56,8 @@ func New(network, dataDir string) (*Store, error) { } func (s *Store) Reserve(id string, ip net.IP, rangeID string) (bool, error) { - fname := filepath.Join(s.dataDir, ip.String()) + fname := GetEscapedPath(s.dataDir, ip.String()) + f, err := os.OpenFile(fname, os.O_RDWR|os.O_EXCL|os.O_CREATE, 0644) if os.IsExist(err) { return false, nil @@ -73,7 +75,7 @@ func (s *Store) Reserve(id string, ip net.IP, rangeID string) (bool, error) { return false, err } // store the reserved ip in lastIPFile - ipfile := filepath.Join(s.dataDir, lastIPFilePrefix+rangeID) + ipfile := GetEscapedPath(s.dataDir, lastIPFilePrefix+rangeID) err = ioutil.WriteFile(ipfile, []byte(ip.String()), 0644) if err != nil { return false, err @@ -83,7 +85,7 @@ func (s *Store) Reserve(id string, ip net.IP, rangeID string) (bool, error) { // LastReservedIP returns the last reserved IP if exists func (s *Store) LastReservedIP(rangeID string) (net.IP, error) { - ipfile := filepath.Join(s.dataDir, lastIPFilePrefix+rangeID) + ipfile := GetEscapedPath(s.dataDir, lastIPFilePrefix+rangeID) data, err := ioutil.ReadFile(ipfile) if err != nil { return nil, err @@ -92,7 +94,7 @@ func (s *Store) LastReservedIP(rangeID string) (net.IP, error) { } func (s *Store) Release(ip net.IP) error { - return os.Remove(filepath.Join(s.dataDir, ip.String())) + return os.Remove(GetEscapedPath(s.dataDir, ip.String())) } // N.B. This function eats errors to be tolerant and @@ -115,3 +117,10 @@ func (s *Store) ReleaseByID(id string) error { }) return err } + +func GetEscapedPath(dataDir string, fname string) string { + if runtime.GOOS == "windows" { + fname = strings.Replace(fname, ":", "_", -1) + } + return filepath.Join(dataDir, fname) +} diff --git a/plugins/ipam/host-local/backend/disk/disk_suite_test.go b/plugins/ipam/host-local/backend/disk/disk_suite_test.go new file mode 100644 index 000000000..ffdd2f222 --- /dev/null +++ b/plugins/ipam/host-local/backend/disk/disk_suite_test.go @@ -0,0 +1,27 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package disk + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestLock(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Disk Suite") +} diff --git a/plugins/ipam/host-local/backend/disk/lock.go b/plugins/ipam/host-local/backend/disk/lock.go index bd4fce275..fe7b48032 100644 --- a/plugins/ipam/host-local/backend/disk/lock.go +++ b/plugins/ipam/host-local/backend/disk/lock.go @@ -1,4 +1,3 @@ -// +build !windows // Copyright 2015 CNI authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,18 +15,28 @@ package disk import ( + "github.com/alexflint/go-filemutex" "os" - "syscall" + "path" ) // FileLock wraps os.File to be used as a lock using flock type FileLock struct { - f *os.File + f *filemutex.FileMutex } // NewFileLock opens file/dir at path and returns unlocked FileLock object -func NewFileLock(path string) (*FileLock, error) { - f, err := os.Open(path) +func NewFileLock(lockPath string) (*FileLock, error) { + fi, err := os.Stat(lockPath) + if err != nil { + return nil, err + } + + if fi.IsDir() { + lockPath = path.Join(lockPath, "lock") + } + + f, err := filemutex.New(lockPath) if err != nil { return nil, err } @@ -35,17 +44,16 @@ func NewFileLock(path string) (*FileLock, error) { return &FileLock{f}, nil } -// Close closes underlying file func (l *FileLock) Close() error { return l.f.Close() } // Lock acquires an exclusive lock func (l *FileLock) Lock() error { - return syscall.Flock(int(l.f.Fd()), syscall.LOCK_EX) + return l.f.Lock() } // Unlock releases the lock func (l *FileLock) Unlock() error { - return syscall.Flock(int(l.f.Fd()), syscall.LOCK_UN) + return l.f.Unlock() } diff --git a/plugins/ipam/host-local/backend/disk/lock_test.go b/plugins/ipam/host-local/backend/disk/lock_test.go new file mode 100644 index 000000000..f76dafb67 --- /dev/null +++ b/plugins/ipam/host-local/backend/disk/lock_test.go @@ -0,0 +1,63 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package disk + +import ( + "io/ioutil" + "os" + "path/filepath" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Lock Operations", func() { + It("locks a file path", func() { + dir, err := ioutil.TempDir("", "") + Expect(err).ToNot(HaveOccurred()) + defer os.RemoveAll(dir) + + // create a dummy file to lock + path := filepath.Join(dir, "x") + f, err := os.OpenFile(path, os.O_RDONLY|os.O_CREATE, 0666) + Expect(err).ToNot(HaveOccurred()) + err = f.Close() + Expect(err).ToNot(HaveOccurred()) + + // now use it to lock + m, err := NewFileLock(path) + Expect(err).ToNot(HaveOccurred()) + + err = m.Lock() + Expect(err).ToNot(HaveOccurred()) + err = m.Unlock() + Expect(err).ToNot(HaveOccurred()) + }) + + It("locks a folder path", func() { + dir, err := ioutil.TempDir("", "") + Expect(err).ToNot(HaveOccurred()) + defer os.RemoveAll(dir) + + // use the folder to lock + m, err := NewFileLock(dir) + Expect(err).ToNot(HaveOccurred()) + + err = m.Lock() + Expect(err).ToNot(HaveOccurred()) + err = m.Unlock() + Expect(err).ToNot(HaveOccurred()) + }) +}) diff --git a/plugins/ipam/host-local/host_local_test.go b/plugins/ipam/host-local/host_local_test.go index 3e4b0d866..15c636517 100644 --- a/plugins/ipam/host-local/host_local_test.go +++ b/plugins/ipam/host-local/host_local_test.go @@ -28,6 +28,7 @@ import ( "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/plugins/pkg/testutils" + "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/disk" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -37,7 +38,7 @@ var _ = Describe("host-local Operations", func() { const ifname string = "eth0" const nspath string = "/some/where" - tmpDir, err := ioutil.TempDir("", "host_local_artifacts") + tmpDir, err := getTmpDir() Expect(err).NotTo(HaveOccurred()) defer os.RemoveAll(tmpDir) @@ -45,26 +46,26 @@ var _ = Describe("host-local Operations", func() { Expect(err).NotTo(HaveOccurred()) conf := fmt.Sprintf(`{ -"cniVersion": "0.3.1", -"name": "mynet", -"type": "ipvlan", -"master": "foo0", - "ipam": { - "type": "host-local", - "dataDir": "%s", - "resolvConf": "%s/resolv.conf", - "ranges": [ - [{ "subnet": "10.1.2.0/24" }, {"subnet": "10.2.2.0/24"}], - [{ "subnet": "2001:db8:1::0/64" }] - ], - "routes": [ - {"dst": "0.0.0.0/0"}, - {"dst": "::/0"}, - {"dst": "192.168.0.0/16", "gw": "1.1.1.1"}, - {"dst": "2001:db8:2::0/64", "gw": "2001:db8:3::1"} - ] - } -}`, tmpDir, tmpDir) + "cniVersion": "0.3.1", + "name": "mynet", + "type": "ipvlan", + "master": "foo0", + "ipam": { + "type": "host-local", + "dataDir": "%s", + "resolvConf": "%s/resolv.conf", + "ranges": [ + [{ "subnet": "10.1.2.0/24" }, {"subnet": "10.2.2.0/24"}], + [{ "subnet": "2001:db8:1::0/64" }] + ], + "routes": [ + {"dst": "0.0.0.0/0"}, + {"dst": "::/0"}, + {"dst": "192.168.0.0/16", "gw": "1.1.1.1"}, + {"dst": "2001:db8:2::0/64", "gw": "2001:db8:3::1"} + ] + } + }`, tmpDir, tmpDir) args := &skel.CmdArgs{ ContainerID: "dummy", @@ -112,7 +113,7 @@ var _ = Describe("host-local Operations", func() { Expect(err).NotTo(HaveOccurred()) Expect(string(contents)).To(Equal("dummy")) - ipFilePath2 := filepath.Join(tmpDir, "mynet", "2001:db8:1::2") + ipFilePath2 := filepath.Join(tmpDir, disk.GetEscapedPath("mynet", "2001:db8:1::2")) contents, err = ioutil.ReadFile(ipFilePath2) Expect(err).NotTo(HaveOccurred()) Expect(string(contents)).To(Equal("dummy")) @@ -142,21 +143,21 @@ var _ = Describe("host-local Operations", func() { const ifname string = "eth0" const nspath string = "/some/where" - tmpDir, err := ioutil.TempDir("", "host_local_artifacts") + tmpDir, err := getTmpDir() Expect(err).NotTo(HaveOccurred()) defer os.RemoveAll(tmpDir) conf := fmt.Sprintf(`{ - "cniVersion": "0.3.0", - "name": "mynet", - "type": "ipvlan", - "master": "foo0", - "ipam": { - "type": "host-local", - "subnet": "10.1.2.0/24", - "dataDir": "%s" - } -}`, tmpDir) + "cniVersion": "0.3.0", + "name": "mynet", + "type": "ipvlan", + "master": "foo0", + "ipam": { + "type": "host-local", + "subnet": "10.1.2.0/24", + "dataDir": "%s" + } + }`, tmpDir) args := &skel.CmdArgs{ ContainerID: "dummy", @@ -176,7 +177,7 @@ var _ = Describe("host-local Operations", func() { const ifname string = "eth0" const nspath string = "/some/where" - tmpDir, err := ioutil.TempDir("", "host_local_artifacts") + tmpDir, err := getTmpDir() Expect(err).NotTo(HaveOccurred()) defer os.RemoveAll(tmpDir) @@ -184,17 +185,17 @@ var _ = Describe("host-local Operations", func() { Expect(err).NotTo(HaveOccurred()) conf := fmt.Sprintf(`{ - "cniVersion": "0.1.0", - "name": "mynet", - "type": "ipvlan", - "master": "foo0", - "ipam": { - "type": "host-local", - "subnet": "10.1.2.0/24", - "dataDir": "%s", - "resolvConf": "%s/resolv.conf" - } -}`, tmpDir, tmpDir) + "cniVersion": "0.1.0", + "name": "mynet", + "type": "ipvlan", + "master": "foo0", + "ipam": { + "type": "host-local", + "subnet": "10.1.2.0/24", + "dataDir": "%s", + "resolvConf": "%s/resolv.conf" + } + }`, tmpDir, tmpDir) args := &skel.CmdArgs{ ContainerID: "dummy", @@ -245,21 +246,21 @@ var _ = Describe("host-local Operations", func() { const ifname string = "eth0" const nspath string = "/some/where" - tmpDir, err := ioutil.TempDir("", "host_local_artifacts") + tmpDir, err := getTmpDir() Expect(err).NotTo(HaveOccurred()) defer os.RemoveAll(tmpDir) conf := fmt.Sprintf(`{ - "cniVersion": "0.3.1", - "name": "mynet", - "type": "ipvlan", - "master": "foo0", - "ipam": { - "type": "host-local", - "subnet": "10.1.2.0/24", - "dataDir": "%s" - } -}`, tmpDir) + "cniVersion": "0.3.1", + "name": "mynet", + "type": "ipvlan", + "master": "foo0", + "ipam": { + "type": "host-local", + "subnet": "10.1.2.0/24", + "dataDir": "%s" + } + }`, tmpDir) args := &skel.CmdArgs{ ContainerID: " dummy\n ", @@ -296,21 +297,21 @@ var _ = Describe("host-local Operations", func() { const ifname string = "eth0" const nspath string = "/some/where" - tmpDir, err := ioutil.TempDir("", "host_local_artifacts") + tmpDir, err := getTmpDir() Expect(err).NotTo(HaveOccurred()) defer os.RemoveAll(tmpDir) conf := fmt.Sprintf(`{ - "cniVersion": "0.2.0", - "name": "mynet", - "type": "ipvlan", - "master": "foo0", - "ipam": { - "type": "host-local", - "subnet": "10.1.2.0/24", - "dataDir": "%s" - } -}`, tmpDir) + "cniVersion": "0.2.0", + "name": "mynet", + "type": "ipvlan", + "master": "foo0", + "ipam": { + "type": "host-local", + "subnet": "10.1.2.0/24", + "dataDir": "%s" + } + }`, tmpDir) args := &skel.CmdArgs{ ContainerID: "testing", @@ -331,28 +332,28 @@ var _ = Describe("host-local Operations", func() { const ifname string = "eth0" const nspath string = "/some/where" - tmpDir, err := ioutil.TempDir("", "host_local_artifacts") + tmpDir, err := getTmpDir() Expect(err).NotTo(HaveOccurred()) defer os.RemoveAll(tmpDir) conf := fmt.Sprintf(`{ - "cniVersion": "0.3.1", - "name": "mynet", - "type": "ipvlan", - "master": "foo0", - "ipam": { - "type": "host-local", - "dataDir": "%s", - "ranges": [ - [{ "subnet": "10.1.2.0/24" }] - ] - }, - "args": { - "cni": { - "ips": ["10.1.2.88"] - } - } -}`, tmpDir) + "cniVersion": "0.3.1", + "name": "mynet", + "type": "ipvlan", + "master": "foo0", + "ipam": { + "type": "host-local", + "dataDir": "%s", + "ranges": [ + [{ "subnet": "10.1.2.0/24" }] + ] + }, + "args": { + "cni": { + "ips": ["10.1.2.88"] + } + } + }`, tmpDir) args := &skel.CmdArgs{ ContainerID: "dummy", @@ -376,7 +377,7 @@ var _ = Describe("host-local Operations", func() { const ifname string = "eth0" const nspath string = "/some/where" - tmpDir, err := ioutil.TempDir("", "host_local_artifacts") + tmpDir, err := getTmpDir() Expect(err).NotTo(HaveOccurred()) defer os.RemoveAll(tmpDir) @@ -384,24 +385,24 @@ var _ = Describe("host-local Operations", func() { Expect(err).NotTo(HaveOccurred()) conf := fmt.Sprintf(`{ - "cniVersion": "0.3.1", - "name": "mynet", - "type": "ipvlan", - "master": "foo0", - "ipam": { - "type": "host-local", - "dataDir": "%s", - "ranges": [ - [{ "subnet": "10.1.2.0/24" }], - [{ "subnet": "10.1.3.0/24" }] - ] - }, - "args": { - "cni": { - "ips": ["10.1.2.88", "10.1.3.77"] - } - } -}`, tmpDir) + "cniVersion": "0.3.1", + "name": "mynet", + "type": "ipvlan", + "master": "foo0", + "ipam": { + "type": "host-local", + "dataDir": "%s", + "ranges": [ + [{ "subnet": "10.1.2.0/24" }], + [{ "subnet": "10.1.3.0/24" }] + ] + }, + "args": { + "cni": { + "ips": ["10.1.2.88", "10.1.3.77"] + } + } + }`, tmpDir) args := &skel.CmdArgs{ ContainerID: "dummy", @@ -426,7 +427,7 @@ var _ = Describe("host-local Operations", func() { const ifname string = "eth0" const nspath string = "/some/where" - tmpDir, err := ioutil.TempDir("", "host_local_artifacts") + tmpDir, err := getTmpDir() Expect(err).NotTo(HaveOccurred()) defer os.RemoveAll(tmpDir) @@ -434,24 +435,24 @@ var _ = Describe("host-local Operations", func() { Expect(err).NotTo(HaveOccurred()) conf := fmt.Sprintf(`{ - "cniVersion": "0.3.1", - "name": "mynet", - "type": "ipvlan", - "master": "foo0", - "ipam": { - "type": "host-local", - "dataDir": "%s", - "ranges": [ - [{"subnet":"172.16.1.0/24"}, { "subnet": "10.1.2.0/24" }], - [{ "subnet": "2001:db8:1::/24" }] - ] - }, - "args": { - "cni": { - "ips": ["10.1.2.88", "2001:db8:1::999"] - } - } -}`, tmpDir) + "cniVersion": "0.3.1", + "name": "mynet", + "type": "ipvlan", + "master": "foo0", + "ipam": { + "type": "host-local", + "dataDir": "%s", + "ranges": [ + [{"subnet":"172.16.1.0/24"}, { "subnet": "10.1.2.0/24" }], + [{ "subnet": "2001:db8:1::/24" }] + ] + }, + "args": { + "cni": { + "ips": ["10.1.2.88", "2001:db8:1::999"] + } + } + }`, tmpDir) args := &skel.CmdArgs{ ContainerID: "dummy", @@ -476,29 +477,29 @@ var _ = Describe("host-local Operations", func() { const ifname string = "eth0" const nspath string = "/some/where" - tmpDir, err := ioutil.TempDir("", "host_local_artifacts") + tmpDir, err := getTmpDir() Expect(err).NotTo(HaveOccurred()) defer os.RemoveAll(tmpDir) conf := fmt.Sprintf(`{ - "cniVersion": "0.3.1", - "name": "mynet", - "type": "ipvlan", - "master": "foo0", - "ipam": { - "type": "host-local", - "dataDir": "%s", - "ranges": [ - [{ "subnet": "10.1.2.0/24" }], - [{ "subnet": "10.1.3.0/24" }] - ] - }, - "args": { - "cni": { - "ips": ["10.1.2.88", "10.1.2.77"] - } - } -}`, tmpDir) + "cniVersion": "0.3.1", + "name": "mynet", + "type": "ipvlan", + "master": "foo0", + "ipam": { + "type": "host-local", + "dataDir": "%s", + "ranges": [ + [{ "subnet": "10.1.2.0/24" }], + [{ "subnet": "10.1.3.0/24" }] + ] + }, + "args": { + "cni": { + "ips": ["10.1.2.88", "10.1.2.77"] + } + } + }`, tmpDir) args := &skel.CmdArgs{ ContainerID: "dummy", @@ -517,6 +518,15 @@ var _ = Describe("host-local Operations", func() { }) }) +func getTmpDir() (string, error) { + tmpDir, err := ioutil.TempDir("", "host_local_artifacts") + if err == nil { + tmpDir = filepath.ToSlash(tmpDir) + } + + return tmpDir, err +} + func mustCIDR(s string) net.IPNet { ip, n, err := net.ParseCIDR(s) n.IP = ip diff --git a/plugins/linux_only.txt b/plugins/linux_only.txt index f4904b38f..e9b5948d3 100644 --- a/plugins/linux_only.txt +++ b/plugins/linux_only.txt @@ -1,6 +1,5 @@ plugins/host-device plugins/ipam/dhcp -plugins/ipam/host-local plugins/main/bridge plugins/main/ipvlan plugins/main/loopback diff --git a/vendor/github.com/alexflint/go-filemutex/LICENSE b/vendor/github.com/alexflint/go-filemutex/LICENSE new file mode 100644 index 000000000..b48c67357 --- /dev/null +++ b/vendor/github.com/alexflint/go-filemutex/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2010-2017 Alex Flint. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/alexflint/go-filemutex/README.md b/vendor/github.com/alexflint/go-filemutex/README.md new file mode 100644 index 000000000..30d05ff5c --- /dev/null +++ b/vendor/github.com/alexflint/go-filemutex/README.md @@ -0,0 +1,31 @@ +# FileMutex + +FileMutex is similar to `sync.RWMutex`, but also synchronizes across processes. +On Linux, OSX, and other POSIX systems it uses the flock system call. On windows +it uses the LockFileEx and UnlockFileEx system calls. + +```go +import ( + "log" + "github.com/alexflint/go-filemutex" +) + +func main() { + m, err := filemutex.New("/tmp/foo.lock") + if err != nil { + log.Fatalln("Directory did not exist or file could not created") + } + + m.Lock() // Will block until lock can be acquired + + // Code here is protected by the mutex + + m.Unlock() +} +``` + +### Installation + + go get github.com/alexflint/go-filemutex + +Forked from https://github.com/golang/build/tree/master/cmd/builder/filemutex_*.go diff --git a/vendor/github.com/alexflint/go-filemutex/filemutex_flock.go b/vendor/github.com/alexflint/go-filemutex/filemutex_flock.go new file mode 100644 index 000000000..2bb775206 --- /dev/null +++ b/vendor/github.com/alexflint/go-filemutex/filemutex_flock.go @@ -0,0 +1,67 @@ +// Copyright 2013 The 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 darwin dragonfly freebsd linux netbsd openbsd + +package filemutex + +import ( + "syscall" +) + +const ( + mkdirPerm = 0750 +) + +// FileMutex is similar to sync.RWMutex, but also synchronizes across processes. +// This implementation is based on flock syscall. +type FileMutex struct { + fd int +} + +func New(filename string) (*FileMutex, error) { + fd, err := syscall.Open(filename, syscall.O_CREAT|syscall.O_RDONLY, mkdirPerm) + if err != nil { + return nil, err + } + return &FileMutex{fd: fd}, nil +} + +func (m *FileMutex) Lock() error { + if err := syscall.Flock(m.fd, syscall.LOCK_EX); err != nil { + return err + } + return nil +} + +func (m *FileMutex) Unlock() error { + if err := syscall.Flock(m.fd, syscall.LOCK_UN); err != nil { + return err + } + return nil +} + +func (m *FileMutex) RLock() error { + if err := syscall.Flock(m.fd, syscall.LOCK_SH); err != nil { + return err + } + return nil +} + +func (m *FileMutex) RUnlock() error { + if err := syscall.Flock(m.fd, syscall.LOCK_UN); err != nil { + return err + } + return nil +} + +// Close does an Unlock() combined with closing and unlinking the associated +// lock file. You should create a New() FileMutex for every Lock() attempt if +// using Close(). +func (m *FileMutex) Close() error { + if err := syscall.Flock(m.fd, syscall.LOCK_UN); err != nil { + return err + } + return syscall.Close(m.fd) +} diff --git a/vendor/github.com/alexflint/go-filemutex/filemutex_windows.go b/vendor/github.com/alexflint/go-filemutex/filemutex_windows.go new file mode 100644 index 000000000..28797d26e --- /dev/null +++ b/vendor/github.com/alexflint/go-filemutex/filemutex_windows.go @@ -0,0 +1,102 @@ +// Copyright 2013 The 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 filemutex + +import ( + "syscall" + "unsafe" +) + +var ( + modkernel32 = syscall.NewLazyDLL("kernel32.dll") + procLockFileEx = modkernel32.NewProc("LockFileEx") + procUnlockFileEx = modkernel32.NewProc("UnlockFileEx") +) + +const ( + lockfileExclusiveLock = 2 +) + +func lockFileEx(h syscall.Handle, flags, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) { + r1, _, e1 := syscall.Syscall6(procLockFileEx.Addr(), 6, uintptr(h), uintptr(flags), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol))) + if r1 == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func unlockFileEx(h syscall.Handle, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) { + r1, _, e1 := syscall.Syscall6(procUnlockFileEx.Addr(), 5, uintptr(h), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)), 0) + if r1 == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +// FileMutex is similar to sync.RWMutex, but also synchronizes across processes. +// This implementation is based on flock syscall. +type FileMutex struct { + fd syscall.Handle +} + +func New(filename string) (*FileMutex, error) { + fd, err := syscall.CreateFile(&(syscall.StringToUTF16(filename)[0]), syscall.GENERIC_READ|syscall.GENERIC_WRITE, + syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE, nil, syscall.OPEN_ALWAYS, syscall.FILE_ATTRIBUTE_NORMAL, 0) + if err != nil { + return nil, err + } + return &FileMutex{fd: fd}, nil +} + +func (m *FileMutex) Lock() error { + var ol syscall.Overlapped + if err := lockFileEx(m.fd, lockfileExclusiveLock, 0, 1, 0, &ol); err != nil { + return err + } + return nil +} + +func (m *FileMutex) Unlock() error { + var ol syscall.Overlapped + if err := unlockFileEx(m.fd, 0, 1, 0, &ol); err != nil { + return err + } + return nil +} + +func (m *FileMutex) RLock() error { + var ol syscall.Overlapped + if err := lockFileEx(m.fd, 0, 0, 1, 0, &ol); err != nil { + return err + } + return nil +} + +func (m *FileMutex) RUnlock() error { + var ol syscall.Overlapped + if err := unlockFileEx(m.fd, 0, 1, 0, &ol); err != nil { + return err + } + return nil +} + +// Close does an Unlock() combined with closing and unlinking the associated +// lock file. You should create a New() FileMutex for every Lock() attempt if +// using Close(). +func (m *FileMutex) Close() error { + var ol syscall.Overlapped + if err := unlockFileEx(m.fd, 0, 1, 0, &ol); err != nil { + return err + } + return syscall.Close(m.fd) +}