diff --git a/common/cli.go b/common/cli.go index cd2a758ec..0f0008e35 100644 --- a/common/cli.go +++ b/common/cli.go @@ -44,10 +44,3 @@ func RootContext() (context.Context, context.CancelFunc) { }() return ctx, cancel } - -func MustExist(path string) { - const perm = 0764 // user rwx, group rw, other r - if err := os.MkdirAll(path, perm); err != nil { - panic(err) - } -} diff --git a/common/dir/rw_dir.go b/common/dir/rw_dir.go new file mode 100644 index 000000000..e56480a20 --- /dev/null +++ b/common/dir/rw_dir.go @@ -0,0 +1,65 @@ +package dir + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "syscall" + + "github.com/gofrs/flock" + "github.com/ledgerwatch/log/v3" +) + +// Rw - type to represent Read-Write access to directory +// if some code accept this typ - it can be sure: dir exists, no other process now write there +// We have no specific Ro type, just use `string` for Ro directory path +type Rw struct { + dirLock *flock.Flock // prevents concurrent use of instance directory + Path string +} + +func convertFileLockError(err error, dir string) error { + if errno, ok := err.(syscall.Errno); ok && dirInUseErrnos[uint(errno)] { + return fmt.Errorf("%w: %s\n", ErrDirUsed, dir) + } + return err +} + +var dirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true} + +var ( + ErrDirUsed = errors.New("datadir already used by another process") +) + +func OpenRw(dir string) (*Rw, error) { + MustExist(dir) + + // Lock the instance directory to prevent concurrent use by another instance as well as + // accidental use of the instance directory as a database. + l := flock.New(filepath.Join(dir, "LOCK")) + locked, err := l.TryLock() + if err != nil { + return nil, convertFileLockError(err, dir) + } + if !locked { + return nil, fmt.Errorf("%w: %s\n", ErrDirUsed, dir) + } + return &Rw{dirLock: l, Path: dir}, nil +} +func (t *Rw) Close() { + // Release instance directory lock. + if t.dirLock != nil { + if err := t.dirLock.Unlock(); err != nil { + log.Error("Can't release snapshot dir lock", "err", err) + } + t.dirLock = nil + } +} + +func MustExist(path string) { + const perm = 0764 // user rwx, group rw, other r + if err := os.MkdirAll(path, perm); err != nil { + panic(err) + } +} diff --git a/go.mod b/go.mod index c72afb5e2..07918f588 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2 github.com/flanglet/kanzi-go v1.9.0 github.com/go-stack/stack v1.8.1 + github.com/gofrs/flock v0.8.1 github.com/google/btree v1.0.1 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d diff --git a/go.sum b/go.sum index 6b1a9561b..896a79f09 100644 --- a/go.sum +++ b/go.sum @@ -33,6 +33,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=