host-local: Update host-local IPAM to support Windows

This commit is contained in:
Rakesh Kelkar 2017-10-06 19:00:19 -07:00
parent fbced0cccb
commit 47668f6d64
11 changed files with 496 additions and 155 deletions

4
Godeps/Godeps.json generated
View File

@ -6,6 +6,10 @@
"./..."
],
"Deps": [
{
"ImportPath": "github.com/alexflint/go-filemutex",
"Rev": "72bdc8eae2aef913234599b837f5dda445ca9bd9"
},
{
"ImportPath": "github.com/containernetworking/cni/libcni",
"Comment": "v0.6.0-rc1",

View File

@ -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)
}

View File

@ -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")
}

View File

@ -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()
}

View File

@ -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())
})
})

View File

@ -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

View File

@ -1,6 +1,5 @@
plugins/host-device
plugins/ipam/dhcp
plugins/ipam/host-local
plugins/main/bridge
plugins/main/ipvlan
plugins/main/loopback

21
vendor/github.com/alexflint/go-filemutex/LICENSE generated vendored Normal file
View File

@ -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.

31
vendor/github.com/alexflint/go-filemutex/README.md generated vendored Normal file
View File

@ -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

View File

@ -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)
}

View File

@ -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)
}