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 }