You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ro...@apache.org on 2018/10/21 19:17:33 UTC

[cloudstack-cloudmonkey] 05/05: cmk: implement file lock for config read/write

This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack-cloudmonkey.git

commit 47408438ee3be5095504f740395f85e2e59a76d8
Author: Rohit Yadav <ro...@apache.org>
AuthorDate: Mon Oct 22 00:45:58 2018 +0530

    cmk: implement file lock for config read/write
    
    Signed-off-by: Rohit Yadav <ro...@apache.org>
---
 config/config.go                               |  19 ++-
 go.mod                                         |   1 +
 go.sum                                         |   2 +
 vendor/github.com/gofrs/flock/.gitignore       |  24 +++
 vendor/github.com/gofrs/flock/.travis.yml      |  10 ++
 vendor/github.com/gofrs/flock/LICENSE          |  27 ++++
 vendor/github.com/gofrs/flock/README.md        |  40 +++++
 vendor/github.com/gofrs/flock/appveyor.yml     |  25 ++++
 vendor/github.com/gofrs/flock/flock.go         | 127 ++++++++++++++++
 vendor/github.com/gofrs/flock/flock_unix.go    | 195 +++++++++++++++++++++++++
 vendor/github.com/gofrs/flock/flock_winapi.go  |  76 ++++++++++
 vendor/github.com/gofrs/flock/flock_windows.go | 140 ++++++++++++++++++
 vendor/modules.txt                             |   2 +
 13 files changed, 686 insertions(+), 2 deletions(-)

diff --git a/config/config.go b/config/config.go
index 0b0ad6b..42af265 100644
--- a/config/config.go
+++ b/config/config.go
@@ -27,6 +27,7 @@ import (
 	"strconv"
 	"time"
 
+	"github.com/gofrs/flock"
 	"github.com/mitchellh/go-homedir"
 	"gopkg.in/ini.v1"
 )
@@ -140,6 +141,18 @@ func newHTTPClient(cfg *Config) *http.Client {
 }
 
 func reloadConfig(cfg *Config) *Config {
+	fileLock := flock.New(path.Join(getDefaultConfigDir(), "lock"))
+	err := fileLock.Lock()
+	if err != nil {
+		fmt.Println("Failed to grab config file lock, please try again")
+		return cfg
+	}
+	cfg = saveConfig(cfg)
+	fileLock.Unlock()
+	return cfg
+}
+
+func saveConfig(cfg *Config) *Config {
 	if _, err := os.Stat(cfg.Dir); err != nil {
 		os.Mkdir(cfg.Dir, 0700)
 	}
@@ -214,7 +227,7 @@ func reloadConfig(cfg *Config) *Config {
 }
 
 // UpdateConfig updates and saves config
-func (c *Config) UpdateConfig(key string, value string) {
+func (c *Config) UpdateConfig(key string, value string, update bool) {
 	switch key {
 	case "prompt":
 		c.Core.Prompt = value
@@ -246,7 +259,9 @@ func (c *Config) UpdateConfig(key string, value string) {
 		c.Core.VerifyCert = value == "true"
 	}
 
-	reloadConfig(c)
+	if update {
+		reloadConfig(c)
+	}
 }
 
 // NewConfig creates or reload config and loads API cache
diff --git a/go.mod b/go.mod
index ed2a67a..d403cd0 100644
--- a/go.mod
+++ b/go.mod
@@ -21,6 +21,7 @@ require (
 	github.com/briandowns/spinner v0.0.0-20181018151057-dd69c579ff20
 	github.com/c-bata/go-prompt v0.2.2
 	github.com/fatih/color v1.7.0 // indirect
+	github.com/gofrs/flock v0.7.0
 	github.com/google/shlex v0.0.0-20150127133951-6f45313302b9
 	github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
 	github.com/jtolds/gls v4.2.1+incompatible // indirect
diff --git a/go.sum b/go.sum
index d7b7057..5bf1766 100644
--- a/go.sum
+++ b/go.sum
@@ -4,6 +4,8 @@ github.com/c-bata/go-prompt v0.2.2 h1:uyKRz6Z6DUyj49QVijyM339UJV9yhbr70gESwbNU3e
 github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34=
 github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/gofrs/flock v0.7.0 h1:pGFUjl501gafK9HBt1VGL1KCOd/YhIooID+xgyJCf3g=
+github.com/gofrs/flock v0.7.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
 github.com/google/shlex v0.0.0-20150127133951-6f45313302b9 h1:JM174NTeGNJ2m/oLH3UOWOvWQQKd+BoL3hcSCUWFLt0=
 github.com/google/shlex v0.0.0-20150127133951-6f45313302b9/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
diff --git a/vendor/github.com/gofrs/flock/.gitignore b/vendor/github.com/gofrs/flock/.gitignore
new file mode 100644
index 0000000..daf913b
--- /dev/null
+++ b/vendor/github.com/gofrs/flock/.gitignore
@@ -0,0 +1,24 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
diff --git a/vendor/github.com/gofrs/flock/.travis.yml b/vendor/github.com/gofrs/flock/.travis.yml
new file mode 100644
index 0000000..b791a74
--- /dev/null
+++ b/vendor/github.com/gofrs/flock/.travis.yml
@@ -0,0 +1,10 @@
+language: go
+go:
+  - 1.10.x
+  - 1.11.x
+script: go test -v -check.vv -race ./...
+sudo: false
+notifications:
+  email:
+    on_success: never
+    on_failure: always
diff --git a/vendor/github.com/gofrs/flock/LICENSE b/vendor/github.com/gofrs/flock/LICENSE
new file mode 100644
index 0000000..aff7d35
--- /dev/null
+++ b/vendor/github.com/gofrs/flock/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2015, Tim Heckman
+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 linode-netint 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 HOLDER 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/vendor/github.com/gofrs/flock/README.md b/vendor/github.com/gofrs/flock/README.md
new file mode 100644
index 0000000..42d580f
--- /dev/null
+++ b/vendor/github.com/gofrs/flock/README.md
@@ -0,0 +1,40 @@
+# flock
+[![TravisCI Build Status](https://img.shields.io/travis/gofrs/flock/master.svg?style=flat)](https://travis-ci.org/gofrs/flock)
+[![GoDoc](https://img.shields.io/badge/godoc-go--flock-blue.svg?style=flat)](https://godoc.org/github.com/gofrs/flock)
+[![License](https://img.shields.io/badge/license-BSD_3--Clause-brightgreen.svg?style=flat)](https://github.com/gofrs/flock/blob/master/LICENSE)
+
+`flock` implements a thread-safe sync.Locker interface for file locking. It also
+includes a non-blocking TryLock() function to allow locking without blocking execution.
+
+## License
+`flock` is released under the BSD 3-Clause License. See the `LICENSE` file for more details.
+
+## Go Compatibility
+This package makes use of the `context` package that was introduced in Go 1.7. As such, this
+package has an implicit dependency on Go 1.7+.
+
+## Installation
+```
+go get -u github.com/gofrs/flock
+```
+
+## Usage
+```Go
+import "github.com/gofrs/flock"
+
+fileLock := flock.New("/var/lock/go-lock.lock")
+
+locked, err := fileLock.TryLock()
+
+if err != nil {
+	// handle locking error
+}
+
+if locked {
+	// do work
+	fileLock.Unlock()
+}
+```
+
+For more detailed usage information take a look at the package API docs on
+[GoDoc](https://godoc.org/github.com/gofrs/flock).
diff --git a/vendor/github.com/gofrs/flock/appveyor.yml b/vendor/github.com/gofrs/flock/appveyor.yml
new file mode 100644
index 0000000..6848e94
--- /dev/null
+++ b/vendor/github.com/gofrs/flock/appveyor.yml
@@ -0,0 +1,25 @@
+version: '{build}'
+
+build: false
+deploy: false
+
+clone_folder: 'c:\gopath\src\github.com\gofrs\flock'
+
+environment:
+  GOPATH: 'c:\gopath'
+  GOVERSION: '1.11'
+
+init:
+  - git config --global core.autocrlf input
+
+install:
+  - rmdir c:\go /s /q
+  - appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-amd64.msi
+  - msiexec /i go%GOVERSION%.windows-amd64.msi /q
+  - set Path=c:\go\bin;c:\gopath\bin;%Path%
+  - go version
+  - go env
+
+test_script:
+  - go get -t ./...
+  - go test -race -v ./...
diff --git a/vendor/github.com/gofrs/flock/flock.go b/vendor/github.com/gofrs/flock/flock.go
new file mode 100644
index 0000000..5783a49
--- /dev/null
+++ b/vendor/github.com/gofrs/flock/flock.go
@@ -0,0 +1,127 @@
+// Copyright 2015 Tim Heckman. All rights reserved.
+// Use of this source code is governed by the BSD 3-Clause
+// license that can be found in the LICENSE file.
+
+// Package flock implements a thread-safe sync.Locker interface for file locking.
+// It also includes a non-blocking TryLock() function to allow locking
+// without blocking execution.
+//
+// Package flock is released under the BSD 3-Clause License. See the LICENSE file
+// for more details.
+//
+// While using this library, remember that the locking behaviors are not
+// guaranteed to be the same on each platform. For example, some UNIX-like
+// operating systems will transparently convert a shared lock to an exclusive
+// lock. If you Unlock() the flock from a location where you believe that you
+// have the shared lock, you may accidently drop the exclusive lock.
+package flock
+
+import (
+	"context"
+	"os"
+	"sync"
+	"time"
+)
+
+// Flock is the struct type to handle file locking. All fields are unexported,
+// with access to some of the fields provided by getter methods (Path() and Locked()).
+type Flock struct {
+	path string
+	m    sync.RWMutex
+	fh   *os.File
+	l    bool
+	r    bool
+}
+
+// New returns a new instance of *Flock. The only parameter
+// it takes is the path to the desired lockfile.
+func New(path string) *Flock {
+	return &Flock{path: path}
+}
+
+// NewFlock returns a new instance of *Flock. The only parameter
+// it takes is the path to the desired lockfile.
+//
+// Deprecated: Use New instead.
+func NewFlock(path string) *Flock {
+	return New(path)
+}
+
+// Close is equivalent to calling Unlock.
+//
+// This will release the lock and close the underlying file descriptor.
+// It will not remove the file from disk, that's up to your application.
+func (f *Flock) Close() error {
+	return f.Unlock()
+}
+
+// Path returns the path as provided in NewFlock().
+func (f *Flock) Path() string {
+	return f.path
+}
+
+// Locked returns the lock state (locked: true, unlocked: false).
+//
+// Warning: by the time you use the returned value, the state may have changed.
+func (f *Flock) Locked() bool {
+	f.m.RLock()
+	defer f.m.RUnlock()
+	return f.l
+}
+
+// RLocked returns the read lock state (locked: true, unlocked: false).
+//
+// Warning: by the time you use the returned value, the state may have changed.
+func (f *Flock) RLocked() bool {
+	f.m.RLock()
+	defer f.m.RUnlock()
+	return f.r
+}
+
+func (f *Flock) String() string {
+	return f.path
+}
+
+// TryLockContext repeatedly tries to take an exclusive lock until one of the
+// conditions is met: TryLock succeeds, TryLock fails with error, or Context
+// Done channel is closed.
+func (f *Flock) TryLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) {
+	return tryCtx(f.TryLock, ctx, retryDelay)
+}
+
+// TryRLockContext repeatedly tries to take a shared lock until one of the
+// conditions is met: TryRLock succeeds, TryRLock fails with error, or Context
+// Done channel is closed.
+func (f *Flock) TryRLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) {
+	return tryCtx(f.TryRLock, ctx, retryDelay)
+}
+
+func tryCtx(fn func() (bool, error), ctx context.Context, retryDelay time.Duration) (bool, error) {
+	if ctx.Err() != nil {
+		return false, ctx.Err()
+	}
+	for {
+		if ok, err := fn(); ok || err != nil {
+			return ok, err
+		}
+		select {
+		case <-ctx.Done():
+			return false, ctx.Err()
+		case <-time.After(retryDelay):
+			// try again
+		}
+	}
+}
+
+func (f *Flock) setFh() error {
+	// open a new os.File instance
+	// create it if it doesn't exist, and open the file read-only.
+	fh, err := os.OpenFile(f.path, os.O_CREATE|os.O_RDONLY, os.FileMode(0600))
+	if err != nil {
+		return err
+	}
+
+	// set the filehandle on the struct
+	f.fh = fh
+	return nil
+}
diff --git a/vendor/github.com/gofrs/flock/flock_unix.go b/vendor/github.com/gofrs/flock/flock_unix.go
new file mode 100644
index 0000000..45f71a7
--- /dev/null
+++ b/vendor/github.com/gofrs/flock/flock_unix.go
@@ -0,0 +1,195 @@
+// Copyright 2015 Tim Heckman. All rights reserved.
+// Use of this source code is governed by the BSD 3-Clause
+// license that can be found in the LICENSE file.
+
+// +build !windows
+
+package flock
+
+import (
+	"os"
+	"syscall"
+)
+
+// Lock is a blocking call to try and take an exclusive file lock. It will wait
+// until it is able to obtain the exclusive file lock. It's recommended that
+// TryLock() be used over this function. This function may block the ability to
+// query the current Locked() or RLocked() status due to a RW-mutex lock.
+//
+// If we are already exclusive-locked, this function short-circuits and returns
+// immediately assuming it can take the mutex lock.
+//
+// If the *Flock has a shared lock (RLock), this may transparently replace the
+// shared lock with an exclusive lock on some UNIX-like operating systems. Be
+// careful when using exclusive locks in conjunction with shared locks
+// (RLock()), because calling Unlock() may accidentally release the exclusive
+// lock that was once a shared lock.
+func (f *Flock) Lock() error {
+	return f.lock(&f.l, syscall.LOCK_EX)
+}
+
+// RLock is a blocking call to try and take a shared file lock. It will wait
+// until it is able to obtain the shared file lock. It's recommended that
+// TryRLock() be used over this function. This function may block the ability to
+// query the current Locked() or RLocked() status due to a RW-mutex lock.
+//
+// If we are already shared-locked, this function short-circuits and returns
+// immediately assuming it can take the mutex lock.
+func (f *Flock) RLock() error {
+	return f.lock(&f.r, syscall.LOCK_SH)
+}
+
+func (f *Flock) lock(locked *bool, flag int) error {
+	f.m.Lock()
+	defer f.m.Unlock()
+
+	if *locked {
+		return nil
+	}
+
+	if f.fh == nil {
+		if err := f.setFh(); err != nil {
+			return err
+		}
+	}
+
+	if err := syscall.Flock(int(f.fh.Fd()), flag); err != nil {
+		shouldRetry, reopenErr := f.reopenFDOnError(err)
+		if reopenErr != nil {
+			return reopenErr
+		}
+
+		if !shouldRetry {
+			return err
+		}
+
+		if err = syscall.Flock(int(f.fh.Fd()), flag); err != nil {
+			return err
+		}
+	}
+
+	*locked = true
+	return nil
+}
+
+// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so
+// while it is running the Locked() and RLocked() functions will be blocked.
+//
+// This function short-circuits if we are unlocked already. If not, it calls
+// syscall.LOCK_UN on the file and closes the file descriptor. It does not
+// remove the file from disk. It's up to your application to do.
+//
+// Please note, if your shared lock became an exclusive lock this may
+// unintentionally drop the exclusive lock if called by the consumer that
+// believes they have a shared lock. Please see Lock() for more details.
+func (f *Flock) Unlock() error {
+	f.m.Lock()
+	defer f.m.Unlock()
+
+	// if we aren't locked or if the lockfile instance is nil
+	// just return a nil error because we are unlocked
+	if (!f.l && !f.r) || f.fh == nil {
+		return nil
+	}
+
+	// mark the file as unlocked
+	if err := syscall.Flock(int(f.fh.Fd()), syscall.LOCK_UN); err != nil {
+		return err
+	}
+
+	f.fh.Close()
+
+	f.l = false
+	f.r = false
+	f.fh = nil
+
+	return nil
+}
+
+// TryLock is the preferred function for taking an exclusive file lock. This
+// function takes an RW-mutex lock before it tries to lock the file, so there is
+// the possibility that this function may block for a short time if another
+// goroutine is trying to take any action.
+//
+// The actual file lock is non-blocking. If we are unable to get the exclusive
+// file lock, the function will return false instead of waiting for the lock. If
+// we get the lock, we also set the *Flock instance as being exclusive-locked.
+func (f *Flock) TryLock() (bool, error) {
+	return f.try(&f.l, syscall.LOCK_EX)
+}
+
+// TryRLock is the preferred function for taking a shared file lock. This
+// function takes an RW-mutex lock before it tries to lock the file, so there is
+// the possibility that this function may block for a short time if another
+// goroutine is trying to take any action.
+//
+// The actual file lock is non-blocking. If we are unable to get the shared file
+// lock, the function will return false instead of waiting for the lock. If we
+// get the lock, we also set the *Flock instance as being share-locked.
+func (f *Flock) TryRLock() (bool, error) {
+	return f.try(&f.r, syscall.LOCK_SH)
+}
+
+func (f *Flock) try(locked *bool, flag int) (bool, error) {
+	f.m.Lock()
+	defer f.m.Unlock()
+
+	if *locked {
+		return true, nil
+	}
+
+	if f.fh == nil {
+		if err := f.setFh(); err != nil {
+			return false, err
+		}
+	}
+
+	var retried bool
+retry:
+	err := syscall.Flock(int(f.fh.Fd()), flag|syscall.LOCK_NB)
+
+	switch err {
+	case syscall.EWOULDBLOCK:
+		return false, nil
+	case nil:
+		*locked = true
+		return true, nil
+	}
+	if !retried {
+		if shouldRetry, reopenErr := f.reopenFDOnError(err); reopenErr != nil {
+			return false, reopenErr
+		} else if shouldRetry {
+			retried = true
+			goto retry
+		}
+	}
+
+	return false, err
+}
+
+// reopenFDOnError determines whether we should reopen the file handle
+// in readwrite mode and try again. This comes from util-linux/sys-utils/flock.c:
+//  Since Linux 3.4 (commit 55725513)
+//  Probably NFSv4 where flock() is emulated by fcntl().
+func (f *Flock) reopenFDOnError(err error) (bool, error) {
+	if err != syscall.EIO && err != syscall.EBADF {
+		return false, nil
+	}
+	if st, err := f.fh.Stat(); err == nil {
+		// if the file is able to be read and written
+		if st.Mode()&0600 == 0600 {
+			f.fh.Close()
+			f.fh = nil
+
+			// reopen in read-write mode and set the filehandle
+			fh, err := os.OpenFile(f.path, os.O_CREATE|os.O_RDWR, os.FileMode(0600))
+			if err != nil {
+				return false, err
+			}
+			f.fh = fh
+			return true, nil
+		}
+	}
+
+	return false, nil
+}
diff --git a/vendor/github.com/gofrs/flock/flock_winapi.go b/vendor/github.com/gofrs/flock/flock_winapi.go
new file mode 100644
index 0000000..fe405a2
--- /dev/null
+++ b/vendor/github.com/gofrs/flock/flock_winapi.go
@@ -0,0 +1,76 @@
+// Copyright 2015 Tim Heckman. All rights reserved.
+// Use of this source code is governed by the BSD 3-Clause
+// license that can be found in the LICENSE file.
+
+// +build windows
+
+package flock
+
+import (
+	"syscall"
+	"unsafe"
+)
+
+var (
+	kernel32, _         = syscall.LoadLibrary("kernel32.dll")
+	procLockFileEx, _   = syscall.GetProcAddress(kernel32, "LockFileEx")
+	procUnlockFileEx, _ = syscall.GetProcAddress(kernel32, "UnlockFileEx")
+)
+
+const (
+	winLockfileFailImmediately = 0x00000001
+	winLockfileExclusiveLock   = 0x00000002
+	winLockfileSharedLock      = 0x00000000
+)
+
+// Use of 0x00000000 for the shared lock is a guess based on some the MS Windows
+// `LockFileEX` docs, which document the `LOCKFILE_EXCLUSIVE_LOCK` flag as:
+//
+// > The function requests an exclusive lock. Otherwise, it requests a shared
+// > lock.
+//
+// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx
+
+func lockFileEx(handle syscall.Handle, flags uint32, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (bool, syscall.Errno) {
+	r1, _, errNo := syscall.Syscall6(
+		uintptr(procLockFileEx),
+		6,
+		uintptr(handle),
+		uintptr(flags),
+		uintptr(reserved),
+		uintptr(numberOfBytesToLockLow),
+		uintptr(numberOfBytesToLockHigh),
+		uintptr(unsafe.Pointer(offset)))
+
+	if r1 != 1 {
+		if errNo == 0 {
+			return false, syscall.EINVAL
+		}
+
+		return false, errNo
+	}
+
+	return true, 0
+}
+
+func unlockFileEx(handle syscall.Handle, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (bool, syscall.Errno) {
+	r1, _, errNo := syscall.Syscall6(
+		uintptr(procUnlockFileEx),
+		5,
+		uintptr(handle),
+		uintptr(reserved),
+		uintptr(numberOfBytesToLockLow),
+		uintptr(numberOfBytesToLockHigh),
+		uintptr(unsafe.Pointer(offset)),
+		0)
+
+	if r1 != 1 {
+		if errNo == 0 {
+			return false, syscall.EINVAL
+		}
+
+		return false, errNo
+	}
+
+	return true, 0
+}
diff --git a/vendor/github.com/gofrs/flock/flock_windows.go b/vendor/github.com/gofrs/flock/flock_windows.go
new file mode 100644
index 0000000..9f4a5f1
--- /dev/null
+++ b/vendor/github.com/gofrs/flock/flock_windows.go
@@ -0,0 +1,140 @@
+// Copyright 2015 Tim Heckman. All rights reserved.
+// Use of this source code is governed by the BSD 3-Clause
+// license that can be found in the LICENSE file.
+
+package flock
+
+import (
+	"syscall"
+)
+
+// ErrorLockViolation is the error code returned from the Windows syscall when a
+// lock would block and you ask to fail immediately.
+const ErrorLockViolation syscall.Errno = 0x21 // 33
+
+// Lock is a blocking call to try and take an exclusive file lock. It will wait
+// until it is able to obtain the exclusive file lock. It's recommended that
+// TryLock() be used over this function. This function may block the ability to
+// query the current Locked() or RLocked() status due to a RW-mutex lock.
+//
+// If we are already locked, this function short-circuits and returns
+// immediately assuming it can take the mutex lock.
+func (f *Flock) Lock() error {
+	return f.lock(&f.l, winLockfileExclusiveLock)
+}
+
+// RLock is a blocking call to try and take a shared file lock. It will wait
+// until it is able to obtain the shared file lock. It's recommended that
+// TryRLock() be used over this function. This function may block the ability to
+// query the current Locked() or RLocked() status due to a RW-mutex lock.
+//
+// If we are already locked, this function short-circuits and returns
+// immediately assuming it can take the mutex lock.
+func (f *Flock) RLock() error {
+	return f.lock(&f.r, winLockfileSharedLock)
+}
+
+func (f *Flock) lock(locked *bool, flag uint32) error {
+	f.m.Lock()
+	defer f.m.Unlock()
+
+	if *locked {
+		return nil
+	}
+
+	if f.fh == nil {
+		if err := f.setFh(); err != nil {
+			return err
+		}
+	}
+
+	if _, errNo := lockFileEx(syscall.Handle(f.fh.Fd()), flag, 0, 1, 0, &syscall.Overlapped{}); errNo > 0 {
+		return errNo
+	}
+
+	*locked = true
+	return nil
+}
+
+// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so
+// while it is running the Locked() and RLocked() functions will be blocked.
+//
+// This function short-circuits if we are unlocked already. If not, it calls
+// UnlockFileEx() on the file and closes the file descriptor. It does not remove
+// the file from disk. It's up to your application to do.
+func (f *Flock) Unlock() error {
+	f.m.Lock()
+	defer f.m.Unlock()
+
+	// if we aren't locked or if the lockfile instance is nil
+	// just return a nil error because we are unlocked
+	if (!f.l && !f.r) || f.fh == nil {
+		return nil
+	}
+
+	// mark the file as unlocked
+	if _, errNo := unlockFileEx(syscall.Handle(f.fh.Fd()), 0, 1, 0, &syscall.Overlapped{}); errNo > 0 {
+		return errNo
+	}
+
+	f.fh.Close()
+
+	f.l = false
+	f.r = false
+	f.fh = nil
+
+	return nil
+}
+
+// TryLock is the preferred function for taking an exclusive file lock. This
+// function does take a RW-mutex lock before it tries to lock the file, so there
+// is the possibility that this function may block for a short time if another
+// goroutine is trying to take any action.
+//
+// The actual file lock is non-blocking. If we are unable to get the exclusive
+// file lock, the function will return false instead of waiting for the lock. If
+// we get the lock, we also set the *Flock instance as being exclusive-locked.
+func (f *Flock) TryLock() (bool, error) {
+	return f.try(&f.l, winLockfileExclusiveLock)
+}
+
+// TryRLock is the preferred function for taking a shared file lock. This
+// function does take a RW-mutex lock before it tries to lock the file, so there
+// is the possibility that this function may block for a short time if another
+// goroutine is trying to take any action.
+//
+// The actual file lock is non-blocking. If we are unable to get the shared file
+// lock, the function will return false instead of waiting for the lock. If we
+// get the lock, we also set the *Flock instance as being shared-locked.
+func (f *Flock) TryRLock() (bool, error) {
+	return f.try(&f.r, winLockfileSharedLock)
+}
+
+func (f *Flock) try(locked *bool, flag uint32) (bool, error) {
+	f.m.Lock()
+	defer f.m.Unlock()
+
+	if *locked {
+		return true, nil
+	}
+
+	if f.fh == nil {
+		if err := f.setFh(); err != nil {
+			return false, err
+		}
+	}
+
+	_, errNo := lockFileEx(syscall.Handle(f.fh.Fd()), flag|winLockfileFailImmediately, 0, 1, 0, &syscall.Overlapped{})
+
+	if errNo > 0 {
+		if errNo == ErrorLockViolation || errNo == syscall.ERROR_IO_PENDING {
+			return false, nil
+		}
+
+		return false, errNo
+	}
+
+	*locked = true
+
+	return true, nil
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index d15237f..b2fda38 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -4,6 +4,8 @@ github.com/briandowns/spinner
 github.com/c-bata/go-prompt
 # github.com/fatih/color v1.7.0
 github.com/fatih/color
+# github.com/gofrs/flock v0.7.0
+github.com/gofrs/flock
 # github.com/google/shlex v0.0.0-20150127133951-6f45313302b9
 github.com/google/shlex
 # github.com/mattn/go-colorable v0.0.9