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