github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/data/dir_data.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 data
     6  
     7  import (
     8  	"github.com/keybase/client/go/kbfs/idutil"
     9  	"github.com/keybase/client/go/kbfs/kbfsblock"
    10  	"github.com/keybase/client/go/kbfs/libkey"
    11  	"github.com/keybase/client/go/kbfs/tlf"
    12  	"github.com/keybase/client/go/libkb"
    13  	"github.com/keybase/client/go/logger"
    14  	"github.com/keybase/client/go/protocol/keybase1"
    15  	"golang.org/x/net/context"
    16  )
    17  
    18  // dirBlockGetter is a function that gets a block suitable for
    19  // reading or writing, and also returns whether the block was already
    20  // dirty.  It may be called from new goroutines, and must handle any
    21  // required locks accordingly.
    22  type dirBlockGetter func(context.Context, libkey.KeyMetadata, BlockPointer,
    23  	Path, BlockReqType) (dblock *DirBlock, wasDirty bool, err error)
    24  
    25  // DirData is a helper struct for accessing and manipulating data
    26  // within a directory.  It's meant for use within a single scope, not
    27  // for long-term storage.  The caller must ensure goroutine-safety.
    28  type DirData struct {
    29  	getter dirBlockGetter
    30  	tree   *blockTree
    31  }
    32  
    33  // NewDirData creates a new DirData instance.
    34  func NewDirData(
    35  	dir Path, chargedTo keybase1.UserOrTeamID, bsplit BlockSplitter,
    36  	kmd libkey.KeyMetadata, getter dirBlockGetter, cacher dirtyBlockCacher,
    37  	log logger.Logger, vlog *libkb.VDebugLog) *DirData {
    38  	dd := &DirData{
    39  		getter: getter,
    40  	}
    41  	dd.tree = &blockTree{
    42  		file:      dir,
    43  		chargedTo: chargedTo,
    44  		kmd:       kmd,
    45  		bsplit:    bsplit,
    46  		getter:    dd.blockGetter,
    47  		cacher:    cacher,
    48  		log:       log,
    49  		vlog:      vlog,
    50  	}
    51  	return dd
    52  }
    53  
    54  func (dd *DirData) rootBlockPointer() BlockPointer {
    55  	return dd.tree.file.TailPointer()
    56  }
    57  
    58  func (dd *DirData) blockGetter(
    59  	ctx context.Context, kmd libkey.KeyMetadata, ptr BlockPointer,
    60  	dir Path, rtype BlockReqType) (
    61  	block BlockWithPtrs, wasDirty bool, err error) {
    62  	return dd.getter(ctx, kmd, ptr, dir, rtype)
    63  }
    64  
    65  var hiddenEntries = map[string]bool{
    66  	".kbfs_git":           true,
    67  	".kbfs_autogit":       true,
    68  	".kbfs_deleted_repos": true,
    69  }
    70  
    71  // GetTopBlock returns the top-most block in this directory block tree.
    72  func (dd *DirData) GetTopBlock(ctx context.Context, rtype BlockReqType) (
    73  	*DirBlock, error) {
    74  	topBlock, _, err := dd.getter(
    75  		ctx, dd.tree.kmd, dd.rootBlockPointer(), dd.tree.file, rtype)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	return topBlock, nil
    80  }
    81  
    82  func (dd *DirData) obfuscator() Obfuscator {
    83  	return dd.tree.file.Obfuscator()
    84  }
    85  
    86  // GetChildren returns a map of all the child EntryInfos in this
    87  // directory.
    88  func (dd *DirData) GetChildren(ctx context.Context) (
    89  	children map[PathPartString]EntryInfo, err error) {
    90  	topBlock, err := dd.GetTopBlock(ctx, BlockRead)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	_, blocks, _, err := dd.tree.getBlocksForOffsetRange(
    96  		ctx, dd.rootBlockPointer(), topBlock, topBlock.FirstOffset(), nil,
    97  		false, true)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	numEntries := 0
   103  	for _, b := range blocks {
   104  		numEntries += len(b.(*DirBlock).Children)
   105  	}
   106  	children = make(map[PathPartString]EntryInfo, numEntries)
   107  	for _, b := range blocks {
   108  		for k, de := range b.(*DirBlock).Children {
   109  			if hiddenEntries[k] {
   110  				continue
   111  			}
   112  			children[NewPathPartString(k, dd.obfuscator())] = de.EntryInfo
   113  		}
   114  	}
   115  	return children, nil
   116  }
   117  
   118  // GetEntries returns a map of all the child DirEntrys in this
   119  // directory.
   120  func (dd *DirData) GetEntries(ctx context.Context) (
   121  	children map[PathPartString]DirEntry, err error) {
   122  	topBlock, err := dd.GetTopBlock(ctx, BlockRead)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	_, blocks, _, err := dd.tree.getBlocksForOffsetRange(
   128  		ctx, dd.rootBlockPointer(), topBlock, topBlock.FirstOffset(), nil,
   129  		false, true)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  
   134  	numEntries := 0
   135  	for _, b := range blocks {
   136  		numEntries += len(b.(*DirBlock).Children)
   137  	}
   138  	children = make(map[PathPartString]DirEntry, numEntries)
   139  	for _, b := range blocks {
   140  		for k, de := range b.(*DirBlock).Children {
   141  			children[NewPathPartString(k, dd.obfuscator())] = de
   142  		}
   143  	}
   144  	return children, nil
   145  }
   146  
   147  // Lookup returns the DirEntry for the given entry named by `name` in
   148  // this directory.
   149  func (dd *DirData) Lookup(ctx context.Context, name PathPartString) (
   150  	DirEntry, error) {
   151  	topBlock, err := dd.GetTopBlock(ctx, BlockRead)
   152  	if err != nil {
   153  		return DirEntry{}, err
   154  	}
   155  
   156  	namePlain := name.Plaintext()
   157  	off := StringOffset(namePlain)
   158  	_, _, block, _, _, _, err := dd.tree.getBlockAtOffset(
   159  		ctx, topBlock, &off, BlockLookup)
   160  	if err != nil {
   161  		return DirEntry{}, err
   162  	}
   163  
   164  	de, ok := block.(*DirBlock).Children[namePlain]
   165  	if !ok {
   166  		return DirEntry{}, idutil.NoSuchNameError{Name: name.String()}
   167  	}
   168  	return de, nil
   169  }
   170  
   171  // createIndirectBlock creates a new indirect block and pick a new id
   172  // for the existing block, and use the existing block's ID for the new
   173  // indirect block that becomes the parent.
   174  func (dd *DirData) createIndirectBlock(ctx context.Context, dver Ver) (
   175  	BlockWithPtrs, error) {
   176  	newID, err := kbfsblock.MakeTemporaryID()
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  	dblock := &DirBlock{
   181  		CommonBlock: CommonBlock{
   182  			IsInd: true,
   183  		},
   184  		IPtrs: []IndirectDirPtr{
   185  			{
   186  				BlockInfo: BlockInfo{
   187  					BlockPointer: BlockPointer{
   188  						ID:      newID,
   189  						KeyGen:  dd.tree.kmd.LatestKeyGeneration(),
   190  						DataVer: dver,
   191  						Context: kbfsblock.MakeFirstContext(
   192  							dd.tree.chargedTo,
   193  							dd.rootBlockPointer().GetBlockType()),
   194  						DirectType: dd.rootBlockPointer().DirectType,
   195  					},
   196  					EncodedSize: 0,
   197  				},
   198  				Off: "",
   199  			},
   200  		},
   201  	}
   202  
   203  	dd.tree.vlog.CLogf(
   204  		ctx, libkb.VLog1, "Creating new level of indirection for dir %v, "+
   205  			"new block id for old top level is %v",
   206  		dd.rootBlockPointer(), newID)
   207  
   208  	err = dd.tree.cacher(ctx, dd.rootBlockPointer(), dblock)
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  
   213  	return dblock, nil
   214  }
   215  
   216  func (dd *DirData) processModifiedBlock(
   217  	ctx context.Context, ptr BlockPointer,
   218  	parentBlocks []ParentBlockAndChildIndex, block *DirBlock) (
   219  	unrefs []BlockInfo, err error) {
   220  	newBlocks, newOffset := dd.tree.bsplit.SplitDirIfNeeded(block)
   221  
   222  	err = dd.tree.cacher(ctx, ptr, block)
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  
   227  	_, newUnrefs, err := dd.tree.markParentsDirty(ctx, parentBlocks)
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  	unrefs = append(unrefs, newUnrefs...)
   232  
   233  	if len(newBlocks) > 1 {
   234  		dd.tree.vlog.CLogf(
   235  			ctx, libkb.VLog1, "Making new right block for %v",
   236  			dd.rootBlockPointer())
   237  
   238  		rightParents, _, err := dd.tree.newRightBlock(
   239  			ctx, parentBlocks, newOffset, FirstValidVer,
   240  			NewDirBlockWithPtrs, dd.createIndirectBlock)
   241  		if err != nil {
   242  			return nil, err
   243  		}
   244  
   245  		if len(parentBlocks) == 0 {
   246  			// We just created the first level of indirection. In that
   247  			// case `newRightBlock` doesn't cache the old top block,
   248  			// so we should do it here.
   249  			err = dd.tree.cacher(
   250  				ctx, rightParents[0].pblock.(*DirBlock).IPtrs[0].BlockPointer,
   251  				newBlocks[0])
   252  			if err != nil {
   253  				return nil, err
   254  			}
   255  		}
   256  
   257  		// Cache the split block in place of the blank one made by
   258  		// `newRightBlock`.
   259  		pb := rightParents[len(rightParents)-1]
   260  		err = dd.tree.cacher(ctx, pb.childBlockPtr(), newBlocks[1])
   261  		if err != nil {
   262  			return nil, err
   263  		}
   264  
   265  		// Shift it over if needed.
   266  		_, newUnrefs, _, err := dd.tree.shiftBlocksToFillHole(ctx, rightParents)
   267  		if err != nil {
   268  			return nil, err
   269  		}
   270  		unrefs = append(unrefs, newUnrefs...)
   271  	}
   272  
   273  	return unrefs, nil
   274  }
   275  
   276  func (dd *DirData) addEntryHelper(
   277  	ctx context.Context, name PathPartString, newDe DirEntry,
   278  	errorIfExists, errorIfNoMatch bool) (
   279  	unrefs []BlockInfo, err error) {
   280  	topBlock, err := dd.GetTopBlock(ctx, BlockWrite)
   281  	if err != nil {
   282  		return nil, err
   283  	}
   284  
   285  	namePlain := name.Plaintext()
   286  	off := StringOffset(namePlain)
   287  	ptr, parentBlocks, block, _, _, _, err := dd.tree.getBlockAtOffset(
   288  		ctx, topBlock, &off, BlockWrite)
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  	dblock := block.(*DirBlock)
   293  
   294  	de, exists := dblock.Children[namePlain]
   295  	if errorIfExists && exists {
   296  		return nil, NameExistsError{name.String()}
   297  	} else if errorIfNoMatch &&
   298  		(!exists || de.BlockPointer != newDe.BlockPointer) {
   299  		return nil, idutil.NoSuchNameError{Name: name.String()}
   300  	}
   301  	dblock.Children[namePlain] = newDe
   302  
   303  	return dd.processModifiedBlock(ctx, ptr, parentBlocks, dblock)
   304  }
   305  
   306  // AddEntry adds a new entry to this directory.
   307  func (dd *DirData) AddEntry(
   308  	ctx context.Context, newName PathPartString, newDe DirEntry) (
   309  	unrefs []BlockInfo, err error) {
   310  	return dd.addEntryHelper(ctx, newName, newDe, true, false)
   311  }
   312  
   313  // UpdateEntry updates an existing entry to this directory.
   314  func (dd *DirData) UpdateEntry(
   315  	ctx context.Context, name PathPartString, newDe DirEntry) (
   316  	unrefs []BlockInfo, err error) {
   317  	return dd.addEntryHelper(ctx, name, newDe, false, true)
   318  }
   319  
   320  // SetEntry set an entry to this directory, whether it is new or existing.
   321  func (dd *DirData) SetEntry(
   322  	ctx context.Context, name PathPartString, newDe DirEntry) (
   323  	unrefs []BlockInfo, err error) {
   324  	return dd.addEntryHelper(ctx, name, newDe, false, false)
   325  }
   326  
   327  // RemoveEntry removes an entry from this directory.
   328  func (dd *DirData) RemoveEntry(ctx context.Context, name PathPartString) (
   329  	unrefs []BlockInfo, err error) {
   330  	topBlock, err := dd.GetTopBlock(ctx, BlockWrite)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  
   335  	namePlain := name.Plaintext()
   336  	off := StringOffset(namePlain)
   337  	ptr, parentBlocks, block, _, _, _, err := dd.tree.getBlockAtOffset(
   338  		ctx, topBlock, &off, BlockWrite)
   339  	if err != nil {
   340  		return nil, err
   341  	}
   342  	dblock := block.(*DirBlock)
   343  
   344  	if _, exists := dblock.Children[namePlain]; !exists {
   345  		// Nothing to do.
   346  		return nil, nil
   347  	}
   348  	delete(dblock.Children, namePlain)
   349  
   350  	// For now, just leave the block empty, at its current place in
   351  	// the tree.  TODO: remove empty blocks all the way up the tree
   352  	// and shift parent pointers around as needed.
   353  	return dd.processModifiedBlock(ctx, ptr, parentBlocks, dblock)
   354  }
   355  
   356  // Ready readies all the dirty child blocks for a directory tree with
   357  // an indirect top-block, and updates their block IDs in their parent
   358  // block's list of indirect pointers.  It returns a map pointing from
   359  // the new block info from any readied block to its corresponding old
   360  // block pointer.
   361  func (dd *DirData) Ready(ctx context.Context, id tlf.ID,
   362  	bcache BlockCache, dirtyBcache IsDirtyProvider,
   363  	rp ReadyProvider, bps BlockPutState,
   364  	topBlock *DirBlock, hashBehavior BlockCacheHashBehavior) (
   365  	map[BlockInfo]BlockPointer, error) {
   366  	return dd.tree.ready(
   367  		ctx, id, bcache, dirtyBcache, rp, bps, topBlock, nil, hashBehavior)
   368  }
   369  
   370  // GetDirtyChildPtrs returns a set of dirty child pointers (not the
   371  // root pointer) for the directory.
   372  func (dd *DirData) GetDirtyChildPtrs(
   373  	ctx context.Context, dirtyBcache IsDirtyProvider) (
   374  	ptrs map[BlockPointer]bool, err error) {
   375  	topBlock, err := dd.GetTopBlock(ctx, BlockRead)
   376  	if err != nil {
   377  		return nil, err
   378  	}
   379  
   380  	if !topBlock.IsIndirect() {
   381  		return nil, nil
   382  	}
   383  
   384  	ptrs = make(map[BlockPointer]bool)
   385  
   386  	// Gather all the paths to all dirty leaf blocks first.
   387  	off := topBlock.FirstOffset()
   388  	for off != nil {
   389  		_, parentBlocks, block, nextBlockOff, _, err :=
   390  			dd.tree.getNextDirtyBlockAtOffset(
   391  				ctx, topBlock, off, BlockWrite, dirtyBcache)
   392  		if err != nil {
   393  			return nil, err
   394  		}
   395  
   396  		if block == nil {
   397  			// No more dirty blocks.
   398  			break
   399  		}
   400  		off = nextBlockOff // Will be `nil` if there are no more blocks.
   401  
   402  		for _, pb := range parentBlocks {
   403  			ptrs[pb.childBlockPtr()] = true
   404  		}
   405  	}
   406  	return ptrs, nil
   407  }
   408  
   409  // GetIndirectDirBlockInfos returns all of the BlockInfos for blocks
   410  // pointed to by indirect blocks within this directory tree.
   411  func (dd *DirData) GetIndirectDirBlockInfos(ctx context.Context) (
   412  	[]BlockInfo, error) {
   413  	return dd.tree.getIndirectBlockInfos(ctx)
   414  }