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 }