blob: 5095bd1d1ca1166d8cd96fe1e02d6c7788cbab66 [file] [log] [blame]
// Copyright 2016 The Vanadium 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 util
import (
"os"
"strings"
"time"
wire "v.io/v23/services/syncbase"
"v.io/v23/verror"
"v.io/x/lib/vlog"
"v.io/x/ref/services/syncbase/store"
"v.io/x/ref/services/syncbase/store/leveldb"
"v.io/x/ref/services/syncbase/store/memstore"
)
type OpenOptions struct {
CreateIfMissing bool
ErrorIfExists bool
}
// OpenStore opens the given store.Store. OpenOptions are respected to the
// degree possible for the specified engine.
func OpenStore(engine, path string, opts OpenOptions) (store.Store, error) {
switch engine {
case "memstore":
if !opts.CreateIfMissing {
return nil, verror.New(verror.ErrInternal, nil, "cannot open memstore")
}
// By definition, the memstore does not already exist.
return memstore.New(), nil
case "leveldb":
leveldbOpts := leveldb.OpenOptions{
CreateIfMissing: opts.CreateIfMissing,
ErrorIfExists: opts.ErrorIfExists,
}
if opts.CreateIfMissing {
// Note, os.MkdirAll is a noop if the path already exists. We rely on
// leveldb to enforce ErrorIfExists.
if err := os.MkdirAll(path, 0700); err != nil {
return nil, verror.New(verror.ErrInternal, nil, err)
}
}
st, err := leveldb.Open(path, leveldbOpts)
if err != nil {
if strings.Contains(err.Error(), "Corruption") {
vlog.Errorf("leveldb %s is corrupt. Moving aside. %v", path, err)
return nil, handleCorruptLevelDB(path)
}
}
return st, err
default:
return nil, verror.New(verror.ErrBadArg, nil, engine)
}
}
// DestroyStore destroys the specified store. Idempotent.
func DestroyStore(engine, path string) error {
switch engine {
case "memstore":
// memstore doesn't persist any data on the disc, do nothing.
return nil
case "leveldb":
if err := os.RemoveAll(path); err != nil {
return verror.New(verror.ErrInternal, nil, err)
}
return nil
default:
return verror.New(verror.ErrBadArg, nil, engine)
}
}
// Moves a corrupt store aside. The app will be responsible for creating a new
// one. Returns an error containing the path of the old store in case the user
// wants to try to debug it.
func handleCorruptLevelDB(path string) error {
newPath := path + ".corrupt." + time.Now().Format(time.RFC3339)
if err := os.Rename(path, newPath); err != nil {
return verror.New(verror.ErrInternal, nil, "leveldb corrupt but could not move aside: "+err.Error())
}
return wire.NewErrCorruptDatabase(nil, newPath)
}