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 5package libkbfs 6 7import ( 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. 20type XattrType int 21 22// New types can only be added at end. 23const ( 24 _ XattrType = iota 25 XattrAppleQuarantine 26) 27 28const ( 29 initialBlockMetadataStoreVersion uint64 = 1 30 currentBlockMetadataStoreVersion uint64 = initialBlockMetadataStoreVersion 31 blockMetadataFolderName string = "kbfs_block_metadata" 32 blockMetadataDbFilename string = "diskBlockMetadata.leveldb" 33) 34 35type 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. 43type 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. 58func 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. 80func (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 101var _ BlockMetadataStore = (*diskBlockMetadataStore)(nil) 102 103// ErrBlockMetadataStoreShutdown is returned when methods are called on 104// diskBlockMetadataStore when it's already shutdown. 105type ErrBlockMetadataStoreShutdown struct{} 106 107// Error implements the error interface. 108func (ErrBlockMetadataStoreShutdown) Error() string { 109 return "disk block metadata store has shutdown" 110} 111 112// GetMetadata implements the BlockMetadataStore interface. 113func (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. 141func (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. 178type 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. 189func 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 198var _ XattrStore = (*xattrStore)(nil) 199 200// GetXattr implements the XattrStore interface. 201func (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. 224func (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. 243type NoopBlockMetadataStore struct{} 244 245var _ BlockMetadataStore = NoopBlockMetadataStore{} 246 247// GetMetadata always returns ldberrors.ErrNotFound. 248func (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. 254func (NoopBlockMetadataStore) UpdateMetadata(ctx context.Context, 255 blockID kbfsblock.ID, updater BlockMetadataUpdater) error { 256 return nil 257} 258 259// Shutdown does nothing. 260func (NoopBlockMetadataStore) Shutdown() {} 261