github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/disk_block_metadata_store.go (about) 1 // Copyright 2018 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package libkbfs 6 7 import ( 8 "context" 9 "sync" 10 11 "github.com/keybase/client/go/kbfs/kbfsblock" 12 "github.com/keybase/client/go/kbfs/kbfscodec" 13 "github.com/keybase/client/go/kbfs/ldbutils" 14 "github.com/keybase/client/go/logger" 15 "github.com/pkg/errors" 16 ldberrors "github.com/syndtr/goleveldb/leveldb/errors" 17 ) 18 19 // XattrType represents the xattr type. 20 type XattrType int 21 22 // New types can only be added at end. 23 const ( 24 _ XattrType = iota 25 XattrAppleQuarantine 26 ) 27 28 const ( 29 initialBlockMetadataStoreVersion uint64 = 1 30 currentBlockMetadataStoreVersion uint64 = initialBlockMetadataStoreVersion 31 blockMetadataFolderName string = "kbfs_block_metadata" 32 blockMetadataDbFilename string = "diskBlockMetadata.leveldb" 33 ) 34 35 type diskBlockMetadataStoreConfig interface { 36 Codec() kbfscodec.Codec 37 MakeLogger(module string) logger.Logger 38 StorageRoot() string 39 Mode() InitMode 40 } 41 42 // diskBlockMetadataStore interacts with BlockMetadata data storage on disk. 43 type diskBlockMetadataStore struct { 44 log logger.Logger 45 config diskBlockMetadataStoreConfig 46 47 // Track the hit rate and eviction rate. These are goroutine safe. 48 hitMeter *ldbutils.CountMeter 49 missMeter *ldbutils.CountMeter 50 putMeter *ldbutils.CountMeter 51 52 lock sync.RWMutex 53 db *ldbutils.LevelDb 54 shutdownCh chan struct{} 55 } 56 57 // newDiskBlockMetadataStore creates a new disk BlockMetadata storage. 58 func newDiskBlockMetadataStore( 59 config diskBlockMetadataStoreConfig, mode InitMode, storageRoot string) ( 60 BlockMetadataStore, error) { 61 log := config.MakeLogger("BMS") 62 db, err := ldbutils.OpenVersionedLevelDb( 63 log, storageRoot, blockMetadataFolderName, 64 currentBlockMetadataStoreVersion, blockMetadataDbFilename, mode) 65 if err != nil { 66 return nil, err 67 } 68 return &diskBlockMetadataStore{ 69 log: log, 70 config: config, 71 hitMeter: ldbutils.NewCountMeter(), 72 missMeter: ldbutils.NewCountMeter(), 73 putMeter: ldbutils.NewCountMeter(), 74 db: db, 75 shutdownCh: make(chan struct{}), 76 }, err 77 } 78 79 // Shutdown shuts done this storae. 80 func (s *diskBlockMetadataStore) Shutdown() { 81 s.log.Debug("Shutting down diskBlockMetadataStore") 82 s.lock.Lock() 83 defer s.lock.Unlock() 84 // shutdownCh has to be checked under lock, otherwise we can race. 85 select { 86 case <-s.shutdownCh: 87 s.log.Warning("Shutdown called more than once") 88 default: 89 } 90 close(s.shutdownCh) 91 if s.db == nil { 92 return 93 } 94 s.db.Close() 95 s.db = nil 96 s.hitMeter.Shutdown() 97 s.missMeter.Shutdown() 98 s.putMeter.Shutdown() 99 } 100 101 var _ BlockMetadataStore = (*diskBlockMetadataStore)(nil) 102 103 // ErrBlockMetadataStoreShutdown is returned when methods are called on 104 // diskBlockMetadataStore when it's already shutdown. 105 type ErrBlockMetadataStoreShutdown struct{} 106 107 // Error implements the error interface. 108 func (ErrBlockMetadataStoreShutdown) Error() string { 109 return "disk block metadata store has shutdown" 110 } 111 112 // GetMetadata implements the BlockMetadataStore interface. 113 func (s *diskBlockMetadataStore) GetMetadata(ctx context.Context, 114 blockID kbfsblock.ID) (value BlockMetadataValue, err error) { 115 s.lock.RLock() 116 defer s.lock.RUnlock() 117 118 select { 119 case <-s.shutdownCh: 120 return BlockMetadataValue{}, ErrBlockMetadataStoreShutdown{} 121 default: 122 } 123 124 encoded, err := s.db.GetWithMeter(blockID.Bytes(), s.hitMeter, s.missMeter) 125 switch errors.Cause(err) { 126 case ldberrors.ErrNotFound: 127 return BlockMetadataValue{}, err 128 case nil: 129 if err = s.config.Codec().Decode(encoded, &value); err != nil { 130 s.log.CWarningf(ctx, "decoding block metadata error: %v", err) 131 return BlockMetadataValue{}, ldberrors.ErrNotFound 132 } 133 return value, nil 134 default: 135 s.log.CWarningf(ctx, "GetMetadata error: %v", err) 136 return BlockMetadataValue{}, ldberrors.ErrNotFound 137 } 138 } 139 140 // UpdateMetadata implements the BlockMetadataStore interface. 141 func (s *diskBlockMetadataStore) UpdateMetadata(ctx context.Context, 142 blockID kbfsblock.ID, updater BlockMetadataUpdater) error { 143 bid := blockID.Bytes() 144 145 s.lock.Lock() 146 defer s.lock.Unlock() 147 148 select { 149 case <-s.shutdownCh: 150 return ErrBlockMetadataStoreShutdown{} 151 default: 152 } 153 154 var value BlockMetadataValue 155 encoded, err := s.db.Get(bid, nil) 156 switch errors.Cause(err) { 157 case ldberrors.ErrNotFound: 158 case nil: 159 if err = s.config.Codec().Decode(encoded, &value); err != nil { 160 s.log.CWarningf(ctx, "decoding block metadata error: %v", err) 161 } 162 default: 163 s.log.CWarningf(ctx, "GetMetadata error: %v", err) 164 } 165 166 if err = updater(&value); err != nil { 167 return err 168 } 169 170 if encoded, err = s.config.Codec().Encode(value); err != nil { 171 return err 172 } 173 return s.db.PutWithMeter(bid, encoded, s.putMeter) 174 } 175 176 // xattrStore is a wrapper around BlockMetadataStore that handles xattr 177 // values. 178 type xattrStore struct { 179 store BlockMetadataStore 180 181 // Track the hit rate and eviction rate. These are goroutine safe. 182 hitMeter *ldbutils.CountMeter 183 missMeter *ldbutils.CountMeter 184 putMeter *ldbutils.CountMeter 185 } 186 187 // NewXattrStoreFromBlockMetadataStore returns a XattrStore which is a wrapper 188 // around the passed in store. 189 func NewXattrStoreFromBlockMetadataStore(store BlockMetadataStore) XattrStore { 190 return xattrStore{ 191 store: store, 192 hitMeter: ldbutils.NewCountMeter(), 193 missMeter: ldbutils.NewCountMeter(), 194 putMeter: ldbutils.NewCountMeter(), 195 } 196 } 197 198 var _ XattrStore = (*xattrStore)(nil) 199 200 // GetXattr implements the XattrStore interface. 201 func (s xattrStore) GetXattr(ctx context.Context, 202 blockID kbfsblock.ID, xattrType XattrType) ([]byte, error) { 203 blockMetadata, err := s.store.GetMetadata(ctx, blockID) 204 switch errors.Cause(err) { 205 case ldberrors.ErrNotFound: 206 s.missMeter.Mark(1) 207 return nil, err 208 case nil: 209 default: 210 return nil, err 211 } 212 213 v, ok := blockMetadata.Xattr[xattrType] 214 if !ok { 215 s.missMeter.Mark(1) 216 return nil, ldberrors.ErrNotFound 217 } 218 219 s.hitMeter.Mark(1) 220 return v, nil 221 } 222 223 // SetXattr implements the XattrStore interface. 224 func (s xattrStore) SetXattr(ctx context.Context, 225 blockID kbfsblock.ID, xattrType XattrType, xattrValue []byte) (err error) { 226 if err = s.store.UpdateMetadata(ctx, blockID, 227 func(v *BlockMetadataValue) error { 228 if v.Xattr == nil { 229 v.Xattr = make(map[XattrType][]byte) 230 } 231 v.Xattr[xattrType] = xattrValue 232 return nil 233 }); err != nil { 234 return err 235 } 236 237 s.putMeter.Mark(1) 238 return nil 239 } 240 241 // NoopBlockMetadataStore satisfies the BlockMetadataStore interface but 242 // does nothing. 243 type NoopBlockMetadataStore struct{} 244 245 var _ BlockMetadataStore = NoopBlockMetadataStore{} 246 247 // GetMetadata always returns ldberrors.ErrNotFound. 248 func (NoopBlockMetadataStore) GetMetadata(ctx context.Context, 249 blockID kbfsblock.ID) (value BlockMetadataValue, err error) { 250 return BlockMetadataValue{}, ldberrors.ErrNotFound 251 } 252 253 // UpdateMetadata returns nil error but does nothing. 254 func (NoopBlockMetadataStore) UpdateMetadata(ctx context.Context, 255 blockID kbfsblock.ID, updater BlockMetadataUpdater) error { 256 return nil 257 } 258 259 // Shutdown does nothing. 260 func (NoopBlockMetadataStore) Shutdown() {}