github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/dataStore_bolt.go (about) 1 //go:build !PSIPHON_USE_BADGER_DB && !PSIPHON_USE_FILES_DB 2 // +build !PSIPHON_USE_BADGER_DB,!PSIPHON_USE_FILES_DB 3 4 /* 5 * Copyright (c) 2018, Psiphon Inc. 6 * All rights reserved. 7 * 8 * This program is free software: you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation, either version 3 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program. If not, see <http://www.gnu.org/licenses/>. 20 * 21 */ 22 23 package psiphon 24 25 import ( 26 std_errors "errors" 27 "fmt" 28 "os" 29 "path/filepath" 30 "runtime/debug" 31 "sync/atomic" 32 "time" 33 34 "github.com/Psiphon-Labs/bolt" 35 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common" 36 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors" 37 ) 38 39 const ( 40 OPEN_DB_RETRIES = 2 41 ) 42 43 type datastoreDB struct { 44 boltDB *bolt.DB 45 filename string 46 isFailed int32 47 } 48 49 type datastoreTx struct { 50 db *datastoreDB 51 boltTx *bolt.Tx 52 } 53 54 type datastoreBucket struct { 55 db *datastoreDB 56 boltBucket *bolt.Bucket 57 } 58 59 type datastoreCursor struct { 60 db *datastoreDB 61 boltCursor *bolt.Cursor 62 } 63 64 func datastoreOpenDB( 65 rootDataDirectory string, retryAndReset bool) (*datastoreDB, error) { 66 67 var db *datastoreDB 68 var err error 69 70 attempts := 1 71 if retryAndReset { 72 attempts += OPEN_DB_RETRIES 73 } 74 75 reset := false 76 77 for attempt := 0; attempt < attempts; attempt++ { 78 79 db, err = tryDatastoreOpenDB(rootDataDirectory, reset) 80 if err == nil { 81 break 82 } 83 84 NoticeWarning("tryDatastoreOpenDB failed: %s", err) 85 86 // The datastore file may be corrupt, so, in subsequent iterations, 87 // set the "reset" flag and attempt to delete the file and try again. 88 // 89 // Don't reset the datastore when open failed due to timeout obtaining 90 // the file lock, as the datastore is simply locked by another 91 // process and not corrupt. As the file lock is advisory, deleting 92 // the file would succeed despite the lock. In this case, still retry 93 // in case the the lock is released. 94 95 reset = !std_errors.Is(err, bolt.ErrTimeout) 96 } 97 98 return db, err 99 } 100 101 func tryDatastoreOpenDB( 102 rootDataDirectory string, reset bool) (retdb *datastoreDB, reterr error) { 103 104 // Testing indicates that the bolt Check function can raise SIGSEGV due to 105 // invalid mmap buffer accesses in cases such as opening a valid but 106 // truncated datastore file. 107 // 108 // To handle this, we temporarily set SetPanicOnFault in order to treat the 109 // fault as a panic, recover any panic, and return an error which will result 110 // in a retry with reset. 111 // 112 // Limitation: another potential crash case is "fatal error: out of 113 // memory" due to bolt.freelist.read attempting to allocate a slice using 114 // a corrupted size value on disk. There is no way to recover from this 115 // fatal. 116 117 // Begin recovery preamble 118 panicOnFault := debug.SetPanicOnFault(true) 119 defer debug.SetPanicOnFault(panicOnFault) 120 121 defer func() { 122 if r := recover(); r != nil { 123 retdb = nil 124 reterr = errors.Tracef("panic: %v", r) 125 } 126 }() 127 // End recovery preamble 128 129 filename := filepath.Join(rootDataDirectory, "psiphon.boltdb") 130 131 if reset { 132 NoticeWarning("tryDatastoreOpenDB: reset") 133 os.Remove(filename) 134 } 135 136 // A typical Psiphon datastore will not have a large, fragmented freelist. 137 // For this reason, we're not setting FreelistType to FreelistMapType or 138 // enabling NoFreelistSync. The latter option has a trade-off of slower 139 // start up time. 140 // 141 // Monitor freelist stats in DataStoreMetrics in diagnostics and consider 142 // setting these options if necessary. 143 144 newDB, err := bolt.Open(filename, 0600, &bolt.Options{Timeout: 1 * time.Second}) 145 if err != nil { 146 return nil, errors.Trace(err) 147 } 148 149 // Run consistency checks on datastore and emit errors for diagnostics 150 // purposes. We assume this will complete quickly for typical size Psiphon 151 // datastores and wait for the check to complete before proceeding. 152 err = newDB.View(func(tx *bolt.Tx) error { 153 return tx.SynchronousCheck() 154 }) 155 if err != nil { 156 return nil, errors.Trace(err) 157 } 158 159 err = newDB.Update(func(tx *bolt.Tx) error { 160 requiredBuckets := [][]byte{ 161 datastoreServerEntriesBucket, 162 datastoreServerEntryTagsBucket, 163 datastoreServerEntryTombstoneTagsBucket, 164 datastoreUrlETagsBucket, 165 datastoreKeyValueBucket, 166 datastoreRemoteServerListStatsBucket, 167 datastoreFailedTunnelStatsBucket, 168 datastoreSLOKsBucket, 169 datastoreTacticsBucket, 170 datastoreSpeedTestSamplesBucket, 171 datastoreDialParametersBucket, 172 } 173 for _, bucket := range requiredBuckets { 174 _, err := tx.CreateBucketIfNotExists(bucket) 175 if err != nil { 176 return err 177 } 178 } 179 return nil 180 }) 181 if err != nil { 182 return nil, errors.Trace(err) 183 } 184 185 // Cleanup obsolete buckets 186 187 err = newDB.Update(func(tx *bolt.Tx) error { 188 obsoleteBuckets := [][]byte{ 189 []byte("tunnelStats"), 190 []byte("rankedServerEntries"), 191 []byte("splitTunnelRouteETags"), 192 []byte("splitTunnelRouteData"), 193 } 194 for _, obsoleteBucket := range obsoleteBuckets { 195 if tx.Bucket(obsoleteBucket) != nil { 196 err := tx.DeleteBucket(obsoleteBucket) 197 if err != nil { 198 NoticeWarning("DeleteBucket %s error: %s", obsoleteBucket, err) 199 // Continue, since this is not fatal 200 } 201 } 202 } 203 return nil 204 }) 205 if err != nil { 206 return nil, errors.Trace(err) 207 } 208 209 return &datastoreDB{ 210 boltDB: newDB, 211 filename: filename, 212 }, nil 213 } 214 215 var errDatastoreFailed = std_errors.New("datastore has failed") 216 217 func (db *datastoreDB) isDatastoreFailed() bool { 218 return atomic.LoadInt32(&db.isFailed) == 1 219 } 220 221 func (db *datastoreDB) setDatastoreFailed(r interface{}) { 222 atomic.StoreInt32(&db.isFailed, 1) 223 NoticeWarning("Datastore failed: %s", errors.Tracef("panic: %v", r)) 224 } 225 226 func (db *datastoreDB) close() error { 227 228 // Limitation: there is no panic recover in this case. We assume boltDB.Close 229 // does not make mmap accesses and prefer to not continue with the datastore 230 // file in a locked or open state. We also assume that any locks aquired by 231 // boltDB.Close, held by transactions, will be released even if the 232 // transaction panics and the database is in the failed state. 233 234 return db.boltDB.Close() 235 } 236 237 func (db *datastoreDB) getDataStoreMetrics() string { 238 fileSize := int64(0) 239 fileInfo, err := os.Stat(db.filename) 240 if err == nil { 241 fileSize = fileInfo.Size() 242 } 243 stats := db.boltDB.Stats() 244 return fmt.Sprintf("filesize %s | freepages %d | freealloc %s | txcount %d | writes %d | writetime %s", 245 common.FormatByteCount(uint64(fileSize)), 246 stats.FreePageN, 247 common.FormatByteCount(uint64(stats.FreeAlloc)), 248 stats.TxN, 249 stats.TxStats.Write, 250 stats.TxStats.WriteTime) 251 } 252 253 func (db *datastoreDB) view(fn func(tx *datastoreTx) error) (reterr error) { 254 255 // Any bolt function that performs mmap buffer accesses can raise SIGBUS due 256 // to underlying storage changes, such as a truncation of the datastore file 257 // or removal or network attached storage, etc. 258 // 259 // To handle this, we temporarily set SetPanicOnFault in order to treat the 260 // fault as a panic, recover any panic to avoid crashing the process, and 261 // putting this datastoreDB instance into a failed state. All subsequent 262 // calls to this datastoreDBinstance or its related datastoreTx and 263 // datastoreBucket instances will fail. 264 265 // Begin recovery preamble 266 if db.isDatastoreFailed() { 267 return errDatastoreFailed 268 } 269 panicOnFault := debug.SetPanicOnFault(true) 270 defer debug.SetPanicOnFault(panicOnFault) 271 defer func() { 272 if r := recover(); r != nil { 273 db.setDatastoreFailed(r) 274 reterr = errDatastoreFailed 275 } 276 }() 277 // End recovery preamble 278 279 return db.boltDB.View( 280 func(tx *bolt.Tx) error { 281 err := fn(&datastoreTx{db: db, boltTx: tx}) 282 if err != nil { 283 return errors.Trace(err) 284 } 285 return nil 286 }) 287 } 288 289 func (db *datastoreDB) update(fn func(tx *datastoreTx) error) (reterr error) { 290 291 // Begin recovery preamble 292 if db.isDatastoreFailed() { 293 return errDatastoreFailed 294 } 295 panicOnFault := debug.SetPanicOnFault(true) 296 defer debug.SetPanicOnFault(panicOnFault) 297 defer func() { 298 if r := recover(); r != nil { 299 db.setDatastoreFailed(r) 300 reterr = errDatastoreFailed 301 } 302 }() 303 // End recovery preamble 304 305 return db.boltDB.Update( 306 func(tx *bolt.Tx) error { 307 err := fn(&datastoreTx{db: db, boltTx: tx}) 308 if err != nil { 309 return errors.Trace(err) 310 } 311 return nil 312 }) 313 } 314 315 func (tx *datastoreTx) bucket(name []byte) (retbucket *datastoreBucket) { 316 317 // Begin recovery preamble 318 if tx.db.isDatastoreFailed() { 319 return &datastoreBucket{db: tx.db, boltBucket: nil} 320 } 321 panicOnFault := debug.SetPanicOnFault(true) 322 defer debug.SetPanicOnFault(panicOnFault) 323 defer func() { 324 if r := recover(); r != nil { 325 tx.db.setDatastoreFailed(r) 326 retbucket = &datastoreBucket{db: tx.db, boltBucket: nil} 327 } 328 }() 329 // End recovery preamble 330 331 return &datastoreBucket{db: tx.db, boltBucket: tx.boltTx.Bucket(name)} 332 } 333 334 func (tx *datastoreTx) clearBucket(name []byte) (reterr error) { 335 336 // Begin recovery preamble 337 if tx.db.isDatastoreFailed() { 338 return errDatastoreFailed 339 } 340 panicOnFault := debug.SetPanicOnFault(true) 341 defer debug.SetPanicOnFault(panicOnFault) 342 defer func() { 343 if r := recover(); r != nil { 344 tx.db.setDatastoreFailed(r) 345 reterr = errDatastoreFailed 346 } 347 }() 348 // End recovery preamble 349 350 err := tx.boltTx.DeleteBucket(name) 351 if err != nil { 352 return errors.Trace(err) 353 } 354 _, err = tx.boltTx.CreateBucket(name) 355 if err != nil { 356 return errors.Trace(err) 357 } 358 return nil 359 } 360 361 func (b *datastoreBucket) get(key []byte) (retvalue []byte) { 362 363 // Begin recovery preamble 364 if b.db.isDatastoreFailed() { 365 return nil 366 } 367 panicOnFault := debug.SetPanicOnFault(true) 368 defer debug.SetPanicOnFault(panicOnFault) 369 defer func() { 370 if r := recover(); r != nil { 371 b.db.setDatastoreFailed(r) 372 retvalue = nil 373 } 374 }() 375 // End recovery preamble 376 377 return b.boltBucket.Get(key) 378 } 379 380 func (b *datastoreBucket) put(key, value []byte) (reterr error) { 381 382 // Begin recovery preamble 383 if b.db.isDatastoreFailed() { 384 return errDatastoreFailed 385 } 386 panicOnFault := debug.SetPanicOnFault(true) 387 defer debug.SetPanicOnFault(panicOnFault) 388 defer func() { 389 if r := recover(); r != nil { 390 b.db.setDatastoreFailed(r) 391 reterr = errDatastoreFailed 392 } 393 }() 394 // End recovery preamble 395 396 err := b.boltBucket.Put(key, value) 397 if err != nil { 398 return errors.Trace(err) 399 } 400 return nil 401 } 402 403 func (b *datastoreBucket) delete(key []byte) (reterr error) { 404 405 // Begin recovery preamble 406 if b.db.isDatastoreFailed() { 407 return errDatastoreFailed 408 } 409 panicOnFault := debug.SetPanicOnFault(true) 410 defer debug.SetPanicOnFault(panicOnFault) 411 defer func() { 412 if r := recover(); r != nil { 413 b.db.setDatastoreFailed(r) 414 reterr = errDatastoreFailed 415 } 416 }() 417 // End recovery preamble 418 419 err := b.boltBucket.Delete(key) 420 if err != nil { 421 return errors.Trace(err) 422 } 423 return nil 424 } 425 426 func (b *datastoreBucket) cursor() (retcursor datastoreCursor) { 427 428 // Begin recovery preamble 429 if b.db.isDatastoreFailed() { 430 return datastoreCursor{db: b.db, boltCursor: nil} 431 } 432 panicOnFault := debug.SetPanicOnFault(true) 433 defer debug.SetPanicOnFault(panicOnFault) 434 defer func() { 435 if r := recover(); r != nil { 436 b.db.setDatastoreFailed(r) 437 retcursor = datastoreCursor{db: b.db, boltCursor: nil} 438 } 439 }() 440 // End recovery preamble 441 442 return datastoreCursor{db: b.db, boltCursor: b.boltBucket.Cursor()} 443 } 444 445 func (c *datastoreCursor) firstKey() (retkey []byte) { 446 447 // Begin recovery preamble 448 if c.db.isDatastoreFailed() { 449 return nil 450 } 451 panicOnFault := debug.SetPanicOnFault(true) 452 defer debug.SetPanicOnFault(panicOnFault) 453 defer func() { 454 if r := recover(); r != nil { 455 c.db.setDatastoreFailed(r) 456 retkey = nil 457 } 458 }() 459 // End recovery preamble 460 461 key, _ := c.boltCursor.First() 462 return key 463 } 464 465 func (c *datastoreCursor) nextKey() (retkey []byte) { 466 467 // Begin recovery preamble 468 if c.db.isDatastoreFailed() { 469 return nil 470 } 471 panicOnFault := debug.SetPanicOnFault(true) 472 defer debug.SetPanicOnFault(panicOnFault) 473 defer func() { 474 if r := recover(); r != nil { 475 c.db.setDatastoreFailed(r) 476 retkey = nil 477 } 478 }() 479 // End recovery preamble 480 481 key, _ := c.boltCursor.Next() 482 return key 483 } 484 485 func (c *datastoreCursor) first() (retkey, retvalue []byte) { 486 487 // Begin recovery preamble 488 if c.db.isDatastoreFailed() { 489 return nil, nil 490 } 491 panicOnFault := debug.SetPanicOnFault(true) 492 defer debug.SetPanicOnFault(panicOnFault) 493 defer func() { 494 if r := recover(); r != nil { 495 c.db.setDatastoreFailed(r) 496 retkey = nil 497 retvalue = nil 498 } 499 }() 500 // End recovery preamble 501 502 return c.boltCursor.First() 503 } 504 505 func (c *datastoreCursor) next() (retkey, retvalue []byte) { 506 507 // Begin recovery preamble 508 if c.db.isDatastoreFailed() { 509 return nil, nil 510 } 511 panicOnFault := debug.SetPanicOnFault(true) 512 defer debug.SetPanicOnFault(panicOnFault) 513 defer func() { 514 if r := recover(); r != nil { 515 c.db.setDatastoreFailed(r) 516 retkey = nil 517 retvalue = nil 518 } 519 }() 520 // End recovery preamble 521 522 return c.boltCursor.Next() 523 } 524 525 func (c *datastoreCursor) close() { 526 // BoltDB doesn't close cursors. 527 }