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  }