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 }