github.com/evdatsion/aphelion-dpos-bft@v0.32.1/libs/db/fsdb.go (about)

     1  package db
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"net/url"
     7  	"os"
     8  	"path/filepath"
     9  	"sort"
    10  	"sync"
    11  
    12  	"github.com/pkg/errors"
    13  
    14  	cmn "github.com/evdatsion/aphelion-dpos-bft/libs/common"
    15  )
    16  
    17  const (
    18  	keyPerm = os.FileMode(0600)
    19  	dirPerm = os.FileMode(0700)
    20  )
    21  
    22  func init() {
    23  	registerDBCreator(FSDBBackend, func(name, dir string) (DB, error) {
    24  		dbPath := filepath.Join(dir, name+".db")
    25  		return NewFSDB(dbPath), nil
    26  	}, false)
    27  }
    28  
    29  var _ DB = (*FSDB)(nil)
    30  
    31  // It's slow.
    32  type FSDB struct {
    33  	mtx sync.Mutex
    34  	dir string
    35  }
    36  
    37  func NewFSDB(dir string) *FSDB {
    38  	err := os.MkdirAll(dir, dirPerm)
    39  	if err != nil {
    40  		panic(errors.Wrap(err, "Creating FSDB dir "+dir))
    41  	}
    42  	database := &FSDB{
    43  		dir: dir,
    44  	}
    45  	return database
    46  }
    47  
    48  func (db *FSDB) Get(key []byte) []byte {
    49  	db.mtx.Lock()
    50  	defer db.mtx.Unlock()
    51  	key = escapeKey(key)
    52  
    53  	path := db.nameToPath(key)
    54  	value, err := read(path)
    55  	if os.IsNotExist(err) {
    56  		return nil
    57  	} else if err != nil {
    58  		panic(errors.Wrapf(err, "Getting key %s (0x%X)", string(key), key))
    59  	}
    60  	return value
    61  }
    62  
    63  func (db *FSDB) Has(key []byte) bool {
    64  	db.mtx.Lock()
    65  	defer db.mtx.Unlock()
    66  	key = escapeKey(key)
    67  
    68  	path := db.nameToPath(key)
    69  	return cmn.FileExists(path)
    70  }
    71  
    72  func (db *FSDB) Set(key []byte, value []byte) {
    73  	db.mtx.Lock()
    74  	defer db.mtx.Unlock()
    75  
    76  	db.SetNoLock(key, value)
    77  }
    78  
    79  func (db *FSDB) SetSync(key []byte, value []byte) {
    80  	db.mtx.Lock()
    81  	defer db.mtx.Unlock()
    82  
    83  	db.SetNoLock(key, value)
    84  }
    85  
    86  // NOTE: Implements atomicSetDeleter.
    87  func (db *FSDB) SetNoLock(key []byte, value []byte) {
    88  	key = escapeKey(key)
    89  	value = nonNilBytes(value)
    90  	path := db.nameToPath(key)
    91  	err := write(path, value)
    92  	if err != nil {
    93  		panic(errors.Wrapf(err, "Setting key %s (0x%X)", string(key), key))
    94  	}
    95  }
    96  
    97  func (db *FSDB) Delete(key []byte) {
    98  	db.mtx.Lock()
    99  	defer db.mtx.Unlock()
   100  
   101  	db.DeleteNoLock(key)
   102  }
   103  
   104  func (db *FSDB) DeleteSync(key []byte) {
   105  	db.mtx.Lock()
   106  	defer db.mtx.Unlock()
   107  
   108  	db.DeleteNoLock(key)
   109  }
   110  
   111  // NOTE: Implements atomicSetDeleter.
   112  func (db *FSDB) DeleteNoLock(key []byte) {
   113  	key = escapeKey(key)
   114  	path := db.nameToPath(key)
   115  	err := remove(path)
   116  	if os.IsNotExist(err) {
   117  		return
   118  	} else if err != nil {
   119  		panic(errors.Wrapf(err, "Removing key %s (0x%X)", string(key), key))
   120  	}
   121  }
   122  
   123  func (db *FSDB) Close() {
   124  	// Nothing to do.
   125  }
   126  
   127  func (db *FSDB) Print() {
   128  	db.mtx.Lock()
   129  	defer db.mtx.Unlock()
   130  
   131  	panic("FSDB.Print not yet implemented")
   132  }
   133  
   134  func (db *FSDB) Stats() map[string]string {
   135  	db.mtx.Lock()
   136  	defer db.mtx.Unlock()
   137  
   138  	panic("FSDB.Stats not yet implemented")
   139  }
   140  
   141  func (db *FSDB) NewBatch() Batch {
   142  	db.mtx.Lock()
   143  	defer db.mtx.Unlock()
   144  
   145  	// Not sure we would ever want to try...
   146  	// It doesn't seem easy for general filesystems.
   147  	panic("FSDB.NewBatch not yet implemented")
   148  }
   149  
   150  func (db *FSDB) Mutex() *sync.Mutex {
   151  	return &(db.mtx)
   152  }
   153  
   154  func (db *FSDB) Iterator(start, end []byte) Iterator {
   155  	return db.MakeIterator(start, end, false)
   156  }
   157  
   158  func (db *FSDB) MakeIterator(start, end []byte, isReversed bool) Iterator {
   159  	db.mtx.Lock()
   160  	defer db.mtx.Unlock()
   161  
   162  	// We need a copy of all of the keys.
   163  	// Not the best, but probably not a bottleneck depending.
   164  	keys, err := list(db.dir, start, end)
   165  	if err != nil {
   166  		panic(errors.Wrapf(err, "Listing keys in %s", db.dir))
   167  	}
   168  	if isReversed {
   169  		sort.Sort(sort.Reverse(sort.StringSlice(keys)))
   170  	} else {
   171  		sort.Strings(keys)
   172  	}
   173  	return newMemDBIterator(db, keys, start, end)
   174  }
   175  
   176  func (db *FSDB) ReverseIterator(start, end []byte) Iterator {
   177  	return db.MakeIterator(start, end, true)
   178  }
   179  
   180  func (db *FSDB) nameToPath(name []byte) string {
   181  	n := url.PathEscape(string(name))
   182  	return filepath.Join(db.dir, n)
   183  }
   184  
   185  // Read some bytes to a file.
   186  // CONTRACT: returns os errors directly without wrapping.
   187  func read(path string) ([]byte, error) {
   188  	f, err := os.Open(path)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  	defer f.Close()
   193  
   194  	d, err := ioutil.ReadAll(f)
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  	return d, nil
   199  }
   200  
   201  // Write some bytes from a file.
   202  // CONTRACT: returns os errors directly without wrapping.
   203  func write(path string, d []byte) error {
   204  	f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, keyPerm)
   205  	if err != nil {
   206  		return err
   207  	}
   208  	defer f.Close()
   209  	// fInfo, err := f.Stat()
   210  	// if err != nil {
   211  	// 	return err
   212  	// }
   213  	// if fInfo.Mode() != keyPerm {
   214  	// 	return tmerrors.NewErrPermissionsChanged(f.Name(), keyPerm, fInfo.Mode())
   215  	// }
   216  	_, err = f.Write(d)
   217  	if err != nil {
   218  		return err
   219  	}
   220  	err = f.Sync()
   221  	return err
   222  }
   223  
   224  // Remove a file.
   225  // CONTRACT: returns os errors directly without wrapping.
   226  func remove(path string) error {
   227  	return os.Remove(path)
   228  }
   229  
   230  // List keys in a directory, stripping of escape sequences and dir portions.
   231  // CONTRACT: returns os errors directly without wrapping.
   232  func list(dirPath string, start, end []byte) ([]string, error) {
   233  	dir, err := os.Open(dirPath)
   234  	if err != nil {
   235  		return nil, err
   236  	}
   237  	defer dir.Close()
   238  
   239  	names, err := dir.Readdirnames(0)
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  	var keys []string
   244  	for _, name := range names {
   245  		n, err := url.PathUnescape(name)
   246  		if err != nil {
   247  			return nil, fmt.Errorf("Failed to unescape %s while listing", name)
   248  		}
   249  		key := unescapeKey([]byte(n))
   250  		if IsKeyInDomain(key, start, end) {
   251  			keys = append(keys, string(key))
   252  		}
   253  	}
   254  	return keys, nil
   255  }
   256  
   257  // To support empty or nil keys, while the file system doesn't allow empty
   258  // filenames.
   259  func escapeKey(key []byte) []byte {
   260  	return []byte("k_" + string(key))
   261  }
   262  func unescapeKey(escKey []byte) []byte {
   263  	if len(escKey) < 2 {
   264  		panic(fmt.Sprintf("Invalid esc key: %x", escKey))
   265  	}
   266  	if string(escKey[:2]) != "k_" {
   267  		panic(fmt.Sprintf("Invalid esc key: %x", escKey))
   268  	}
   269  	return escKey[2:]
   270  }