github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/dirty_bcache_disk.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/data"
    12  	"github.com/keybase/client/go/kbfs/libkey"
    13  	"github.com/keybase/client/go/kbfs/tlf"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  type dirtyBlockCacheDiskConfig interface {
    18  	codecGetter
    19  	cryptoPureGetter
    20  	keyGetterGetter
    21  	blockOpsGetter
    22  }
    23  
    24  type dirtyBlockCacheDiskInfo struct {
    25  	tmpPtr data.BlockPointer
    26  	isDir  bool
    27  }
    28  
    29  func (dbcdi dirtyBlockCacheDiskInfo) newBlock() data.Block {
    30  	if dbcdi.isDir {
    31  		return data.NewDirBlock()
    32  	}
    33  	return data.NewFileBlock()
    34  }
    35  
    36  // DirtyBlockCacheDisk stores dirty blocks in a local disk block
    37  // cache, rather than keeping them in memory.
    38  type DirtyBlockCacheDisk struct {
    39  	config    dirtyBlockCacheDiskConfig
    40  	diskCache *DiskBlockCacheLocal
    41  	kmd       libkey.KeyMetadata
    42  	branch    data.BranchName
    43  
    44  	lock   sync.RWMutex
    45  	blocks map[data.BlockPointer]dirtyBlockCacheDiskInfo
    46  }
    47  
    48  var _ data.DirtyBlockCacheSimple = (*DirtyBlockCacheDisk)(nil)
    49  
    50  func newDirtyBlockCacheDisk(
    51  	config dirtyBlockCacheDiskConfig,
    52  	diskCache *DiskBlockCacheLocal, kmd libkey.KeyMetadata,
    53  	branch data.BranchName) *DirtyBlockCacheDisk {
    54  	return &DirtyBlockCacheDisk{
    55  		config:    config,
    56  		diskCache: diskCache,
    57  		kmd:       kmd,
    58  		branch:    branch,
    59  		blocks:    make(map[data.BlockPointer]dirtyBlockCacheDiskInfo),
    60  	}
    61  }
    62  
    63  func (d *DirtyBlockCacheDisk) getInfo(ptr data.BlockPointer) (
    64  	dirtyBlockCacheDiskInfo, bool) {
    65  	d.lock.RLock()
    66  	defer d.lock.RUnlock()
    67  	info, ok := d.blocks[ptr]
    68  	return info, ok
    69  }
    70  
    71  func (d *DirtyBlockCacheDisk) saveInfo(
    72  	ptr data.BlockPointer, info dirtyBlockCacheDiskInfo) {
    73  	d.lock.Lock()
    74  	defer d.lock.Unlock()
    75  	d.blocks[ptr] = info
    76  }
    77  
    78  // Get implements the DirtyBlockCache interface for
    79  // DirtyBlockCacheDisk.
    80  func (d *DirtyBlockCacheDisk) Get(
    81  	ctx context.Context, tlfID tlf.ID, ptr data.BlockPointer, branch data.BranchName) (
    82  	data.Block, error) {
    83  	if branch != d.branch {
    84  		return nil, errors.Errorf(
    85  			"Branch %s doesn't match branch %s", branch, d.branch)
    86  	}
    87  
    88  	info, ok := d.getInfo(ptr)
    89  	if !ok {
    90  		return nil, data.NoSuchBlockError{ID: ptr.ID}
    91  	}
    92  
    93  	// Look it up under the temp ID, which is an actual hash that can
    94  	// be verified.
    95  	data, serverHalf, _, err := d.diskCache.Get(ctx, tlfID, info.tmpPtr.ID)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	block := info.newBlock()
   101  	err = assembleBlockLocal(
   102  		ctx, d.config.keyGetter(), d.config.Codec(),
   103  		d.config.cryptoPure(), d.kmd, info.tmpPtr, block, data, serverHalf)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	return block, nil
   108  }
   109  
   110  // Put implements the DirtyBlockCache interface for
   111  // DirtyBlockCacheDisk.  Note than any modifications made to `block`
   112  // after the `Put` will require another `Put` call, in order for them
   113  // to be reflected in the next `Get` call for that block pointer.
   114  func (d *DirtyBlockCacheDisk) Put(
   115  	ctx context.Context, tlfID tlf.ID, ptr data.BlockPointer,
   116  	branch data.BranchName, block data.Block) error {
   117  	if branch != d.branch {
   118  		return errors.Errorf(
   119  			"Branch %s doesn't match branch %s", branch, d.branch)
   120  	}
   121  
   122  	// Need to ready the block, since the disk cache expects encrypted
   123  	// data and a block ID that can be verified against that data.
   124  	id, _, readyBlockData, err := d.config.BlockOps().Ready(ctx, d.kmd, block)
   125  	if err != nil {
   126  		return err
   127  	}
   128  
   129  	err = d.diskCache.Put(
   130  		ctx, tlfID, id, readyBlockData.Buf, readyBlockData.ServerHalf)
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	directType := data.DirectBlock
   136  	if block.IsIndirect() {
   137  		directType = data.IndirectBlock
   138  	}
   139  	_, isDir := block.(*data.DirBlock)
   140  
   141  	info := dirtyBlockCacheDiskInfo{
   142  		tmpPtr: data.BlockPointer{
   143  			ID:         id,
   144  			KeyGen:     d.kmd.LatestKeyGeneration(),
   145  			DataVer:    block.DataVersion(),
   146  			DirectType: directType,
   147  		},
   148  		isDir: isDir,
   149  	}
   150  	d.saveInfo(ptr, info)
   151  
   152  	// TODO: have an in-memory LRU cache of limited size to optimize
   153  	// frequent block access?
   154  	return nil
   155  }