github.com/khulnasoft-lab/khulnasoft@v26.0.1-0.20240328202558-330a6f959fe0+incompatible/libnetwork/internal/kvstore/boltdb/boltdb.go (about)

     1  package boltdb
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  	"os"
     8  	"path/filepath"
     9  	"sync"
    10  	"sync/atomic"
    11  	"time"
    12  
    13  	store "github.com/docker/docker/libnetwork/internal/kvstore"
    14  	bolt "go.etcd.io/bbolt"
    15  )
    16  
    17  var (
    18  	// ErrBoltBucketOptionMissing is thrown when boltBcuket config option is missing
    19  	ErrBoltBucketOptionMissing = errors.New("boltBucket config option missing")
    20  )
    21  
    22  const filePerm = 0o644
    23  
    24  // BoltDB type implements the Store interface
    25  type BoltDB struct {
    26  	mu         sync.Mutex
    27  	client     *bolt.DB
    28  	boltBucket []byte
    29  	dbIndex    uint64
    30  	path       string
    31  	timeout    time.Duration
    32  }
    33  
    34  const (
    35  	libkvmetadatalen = 8
    36  	transientTimeout = time.Duration(10) * time.Second
    37  )
    38  
    39  // New opens a new BoltDB connection to the specified path and bucket
    40  func New(endpoint string, options *store.Config) (store.Store, error) {
    41  	if (options == nil) || (len(options.Bucket) == 0) {
    42  		return nil, ErrBoltBucketOptionMissing
    43  	}
    44  
    45  	dir, _ := filepath.Split(endpoint)
    46  	if err := os.MkdirAll(dir, 0o750); err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	db, err := bolt.Open(endpoint, filePerm, &bolt.Options{
    51  		Timeout: options.ConnectionTimeout,
    52  	})
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	timeout := transientTimeout
    58  	if options.ConnectionTimeout != 0 {
    59  		timeout = options.ConnectionTimeout
    60  	}
    61  
    62  	b := &BoltDB{
    63  		client:     db,
    64  		path:       endpoint,
    65  		boltBucket: []byte(options.Bucket),
    66  		timeout:    timeout,
    67  	}
    68  
    69  	return b, nil
    70  }
    71  
    72  // Put the key, value pair. index number metadata is prepended to the value
    73  func (b *BoltDB) Put(key string, value []byte) error {
    74  	b.mu.Lock()
    75  	defer b.mu.Unlock()
    76  
    77  	return b.client.Update(func(tx *bolt.Tx) error {
    78  		bucket, err := tx.CreateBucketIfNotExists(b.boltBucket)
    79  		if err != nil {
    80  			return err
    81  		}
    82  
    83  		dbIndex := atomic.AddUint64(&b.dbIndex, 1)
    84  		dbval := make([]byte, libkvmetadatalen)
    85  		binary.LittleEndian.PutUint64(dbval, dbIndex)
    86  		dbval = append(dbval, value...)
    87  
    88  		return bucket.Put([]byte(key), dbval)
    89  	})
    90  }
    91  
    92  // Exists checks if the key exists inside the store
    93  func (b *BoltDB) Exists(key string) (bool, error) {
    94  	b.mu.Lock()
    95  	defer b.mu.Unlock()
    96  
    97  	var exists bool
    98  	err := b.client.View(func(tx *bolt.Tx) error {
    99  		bucket := tx.Bucket(b.boltBucket)
   100  		if bucket == nil {
   101  			return store.ErrKeyNotFound
   102  		}
   103  
   104  		exists = len(bucket.Get([]byte(key))) > 0
   105  		return nil
   106  	})
   107  	if err != nil {
   108  		return false, err
   109  	}
   110  	if !exists {
   111  		return false, store.ErrKeyNotFound
   112  	}
   113  	return true, nil
   114  }
   115  
   116  // List returns the range of keys starting with the passed in prefix
   117  func (b *BoltDB) List(keyPrefix string) ([]*store.KVPair, error) {
   118  	b.mu.Lock()
   119  	defer b.mu.Unlock()
   120  
   121  	var kv []*store.KVPair
   122  	err := b.client.View(func(tx *bolt.Tx) error {
   123  		bucket := tx.Bucket(b.boltBucket)
   124  		if bucket == nil {
   125  			return store.ErrKeyNotFound
   126  		}
   127  
   128  		cursor := bucket.Cursor()
   129  		prefix := []byte(keyPrefix)
   130  
   131  		for key, v := cursor.Seek(prefix); bytes.HasPrefix(key, prefix); key, v = cursor.Next() {
   132  			dbIndex := binary.LittleEndian.Uint64(v[:libkvmetadatalen])
   133  			v = v[libkvmetadatalen:]
   134  			val := make([]byte, len(v))
   135  			copy(val, v)
   136  
   137  			kv = append(kv, &store.KVPair{
   138  				Key:       string(key),
   139  				Value:     val,
   140  				LastIndex: dbIndex,
   141  			})
   142  		}
   143  		return nil
   144  	})
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  	if len(kv) == 0 {
   149  		return nil, store.ErrKeyNotFound
   150  	}
   151  	return kv, nil
   152  }
   153  
   154  // AtomicDelete deletes a value at "key" if the key
   155  // has not been modified in the meantime, throws an
   156  // error if this is the case
   157  func (b *BoltDB) AtomicDelete(key string, previous *store.KVPair) error {
   158  	b.mu.Lock()
   159  	defer b.mu.Unlock()
   160  
   161  	if previous == nil {
   162  		return store.ErrPreviousNotSpecified
   163  	}
   164  
   165  	return b.client.Update(func(tx *bolt.Tx) error {
   166  		bucket := tx.Bucket(b.boltBucket)
   167  		if bucket == nil {
   168  			return store.ErrKeyNotFound
   169  		}
   170  
   171  		val := bucket.Get([]byte(key))
   172  		if val == nil {
   173  			return store.ErrKeyNotFound
   174  		}
   175  		dbIndex := binary.LittleEndian.Uint64(val[:libkvmetadatalen])
   176  		if dbIndex != previous.LastIndex {
   177  			return store.ErrKeyModified
   178  		}
   179  		return bucket.Delete([]byte(key))
   180  	})
   181  }
   182  
   183  // Delete deletes a value at "key". Unlike AtomicDelete it doesn't check
   184  // whether the deleted key is at a specific version before deleting.
   185  func (b *BoltDB) Delete(key string) error {
   186  	b.mu.Lock()
   187  	defer b.mu.Unlock()
   188  
   189  	return b.client.Update(func(tx *bolt.Tx) error {
   190  		bucket := tx.Bucket(b.boltBucket)
   191  		if bucket == nil || bucket.Get([]byte(key)) == nil {
   192  			return store.ErrKeyNotFound
   193  		}
   194  		return bucket.Delete([]byte(key))
   195  	})
   196  }
   197  
   198  // AtomicPut puts a value at "key" if the key has not been
   199  // modified since the last Put, throws an error if this is the case
   200  func (b *BoltDB) AtomicPut(key string, value []byte, previous *store.KVPair) (*store.KVPair, error) {
   201  	b.mu.Lock()
   202  	defer b.mu.Unlock()
   203  
   204  	var dbIndex uint64
   205  	dbval := make([]byte, libkvmetadatalen)
   206  	err := b.client.Update(func(tx *bolt.Tx) error {
   207  		bucket := tx.Bucket(b.boltBucket)
   208  		if bucket == nil {
   209  			if previous != nil {
   210  				return store.ErrKeyNotFound
   211  			}
   212  			var err error
   213  			bucket, err = tx.CreateBucket(b.boltBucket)
   214  			if err != nil {
   215  				return err
   216  			}
   217  		}
   218  		// AtomicPut is equivalent to Put if previous is nil and the Ky
   219  		// doesn't exist in the DB.
   220  		val := bucket.Get([]byte(key))
   221  		if previous == nil && len(val) != 0 {
   222  			return store.ErrKeyExists
   223  		}
   224  		if previous != nil {
   225  			if len(val) == 0 {
   226  				return store.ErrKeyNotFound
   227  			}
   228  			dbIndex = binary.LittleEndian.Uint64(val[:libkvmetadatalen])
   229  			if dbIndex != previous.LastIndex {
   230  				return store.ErrKeyModified
   231  			}
   232  		}
   233  		dbIndex = atomic.AddUint64(&b.dbIndex, 1)
   234  		binary.LittleEndian.PutUint64(dbval, b.dbIndex)
   235  		dbval = append(dbval, value...)
   236  		return bucket.Put([]byte(key), dbval)
   237  	})
   238  	if err != nil {
   239  		return nil, err
   240  	}
   241  	return &store.KVPair{Key: key, Value: value, LastIndex: dbIndex}, nil
   242  }
   243  
   244  // Close the db connection to the BoltDB
   245  func (b *BoltDB) Close() {
   246  	b.mu.Lock()
   247  	defer b.mu.Unlock()
   248  
   249  	b.client.Close()
   250  }