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() {}