// InitDataStore initializes the singleton instance of dataStore. This // function uses a sync.Once and is safe for use by concurrent goroutines. // The underlying sql.DB connection pool is also safe. // // Note: the sync.Once was more useful when initDataStore was private and // called on-demand by the public functions below. Now we require an explicit // InitDataStore() call with the filename passed in. The on-demand calls // have been replaced by checkInitDataStore() to assert that Init was called. func InitDataStore(config *Config) (err error) { singleton.init.Do(func() { // Need to gather the list of migratable server entries before // initializing the boltdb store (as prepareMigrationEntries // checks for the existence of the bolt db file) migratableServerEntries := prepareMigrationEntries(config) filename := filepath.Join(config.DataStoreDirectory, DATA_STORE_FILENAME) var db *bolt.DB db, err = bolt.Open(filename, 0600, &bolt.Options{Timeout: 1 * time.Second}) if err != nil { // Note: intending to set the err return value for InitDataStore err = fmt.Errorf("initDataStore failed to open database: %s", err) return } err = db.Update(func(tx *bolt.Tx) error { requiredBuckets := []string{ serverEntriesBucket, rankedServerEntriesBucket, splitTunnelRouteETagsBucket, splitTunnelRouteDataBucket, urlETagsBucket, keyValueBucket, tunnelStatsBucket, } for _, bucket := range requiredBuckets { _, err := tx.CreateBucketIfNotExists([]byte(bucket)) if err != nil { return err } } return nil }) if err != nil { err = fmt.Errorf("initDataStore failed to create buckets: %s", err) return } singleton.db = db // The migrateServerEntries function requires the data store is // initialized prior to execution so that migrated entries can be stored if len(migratableServerEntries) > 0 { migrateEntries(migratableServerEntries, filepath.Join(config.DataStoreDirectory, LEGACY_DATA_STORE_FILENAME)) } resetAllTunnelStatsToUnreported() }) return err }
// InitDataStore initializes the singleton instance of dataStore. This // function uses a sync.Once and is safe for use by concurrent goroutines. // The underlying sql.DB connection pool is also safe. // // Note: the sync.Once was more useful when initDataStore was private and // called on-demand by the public functions below. Now we require an explicit // InitDataStore() call with the filename passed in. The on-demand calls // have been replaced by checkInitDataStore() to assert that Init was called. func InitDataStore(config *Config) (err error) { singleton.init.Do(func() { filename := filepath.Join(config.DataStoreDirectory, DATA_STORE_FILENAME) var db *bolt.DB db, err = bolt.Open(filename, 0600, &bolt.Options{Timeout: 1 * time.Second}) if err != nil { // Note: intending to set the err return value for InitDataStore err = fmt.Errorf("initDataStore failed to open database: %s", err) return } err = db.Update(func(tx *bolt.Tx) error { requiredBuckets := []string{ serverEntriesBucket, rankedServerEntriesBucket, splitTunnelRouteETagsBucket, splitTunnelRouteDataBucket, urlETagsBucket, keyValueBucket, } for _, bucket := range requiredBuckets { _, err := tx.CreateBucketIfNotExists([]byte(bucket)) if err != nil { return err } } return nil }) if err != nil { err = fmt.Errorf("initDataStore failed to create buckets: %s", err) return } singleton.db = db }) return err }
// InitDataStore initializes the singleton instance of dataStore. This // function uses a sync.Once and is safe for use by concurrent goroutines. // The underlying sql.DB connection pool is also safe. // // Note: the sync.Once was more useful when initDataStore was private and // called on-demand by the public functions below. Now we require an explicit // InitDataStore() call with the filename passed in. The on-demand calls // have been replaced by checkInitDataStore() to assert that Init was called. func InitDataStore(config *Config) (err error) { singleton.init.Do(func() { // Need to gather the list of migratable server entries before // initializing the boltdb store (as prepareMigrationEntries // checks for the existence of the bolt db file) migratableServerEntries := prepareMigrationEntries(config) filename := filepath.Join(config.DataStoreDirectory, DATA_STORE_FILENAME) var db *bolt.DB db, err = bolt.Open(filename, 0600, &bolt.Options{Timeout: 1 * time.Second}) // The datastore file may be corrupt, so attempt to delete and try again if err != nil { NoticeAlert("retry on initDataStore error: %s", err) os.Remove(filename) db, err = bolt.Open(filename, 0600, &bolt.Options{Timeout: 1 * time.Second}) } if err != nil { // Note: intending to set the err return value for InitDataStore err = fmt.Errorf("initDataStore failed to open database: %s", err) return } err = db.Update(func(tx *bolt.Tx) error { requiredBuckets := []string{ serverEntriesBucket, rankedServerEntriesBucket, splitTunnelRouteETagsBucket, splitTunnelRouteDataBucket, urlETagsBucket, keyValueBucket, tunnelStatsBucket, } for _, bucket := range requiredBuckets { _, err := tx.CreateBucketIfNotExists([]byte(bucket)) if err != nil { return err } } return nil }) if err != nil { err = fmt.Errorf("initDataStore failed to create buckets: %s", err) return } // Run consistency checks on datastore and emit errors for diagnostics purposes // We assume this will complete quickly for typical size Psiphon datastores. db.View(func(tx *bolt.Tx) error { err := <-tx.Check() if err != nil { NoticeAlert("boltdb Check(): %s", err) } return nil }) singleton.db = db // The migrateServerEntries function requires the data store is // initialized prior to execution so that migrated entries can be stored if len(migratableServerEntries) > 0 { migrateEntries(migratableServerEntries, filepath.Join(config.DataStoreDirectory, LEGACY_DATA_STORE_FILENAME)) } resetAllTunnelStatsToUnreported() }) return err }