github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/block_disk_store.go (about)

     1  // Copyright 2016 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  	"path/filepath"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/keybase/client/go/kbfs/ioutil"
    14  	"github.com/keybase/client/go/kbfs/kbfsblock"
    15  	"github.com/keybase/client/go/kbfs/kbfscodec"
    16  	"github.com/keybase/client/go/kbfs/kbfscrypto"
    17  	"github.com/keybase/go-codec/codec"
    18  	"github.com/pkg/errors"
    19  )
    20  
    21  // blockDiskStore stores block data in flat files on disk.
    22  //
    23  // The directory layout looks like:
    24  //
    25  // dir/0100/0...01/data
    26  // dir/0100/0...01/id
    27  // dir/0100/0...01/ksh
    28  // dir/0100/0...01/refs
    29  // ...
    30  // dir/01cc/5...55/id
    31  // dir/01cc/5...55/refs
    32  // ...
    33  // dir/01dd/6...66/data
    34  // dir/01dd/6...66/id
    35  // dir/01dd/6...66/ksh
    36  // ...
    37  // dir/01ff/f...ff/data
    38  // dir/01ff/f...ff/id
    39  // dir/01ff/f...ff/ksh
    40  // dir/01ff/f...ff/refs
    41  //
    42  // Each block has its own subdirectory with its ID truncated to 17
    43  // bytes (34 characters) as a name. The block subdirectories are
    44  // splayed over (# of possible hash types) * 256 subdirectories -- one
    45  // byte for the hash type (currently only one) plus the first byte of
    46  // the hash data -- using the first four characters of the name to
    47  // keep the number of directories in dir itself to a manageable
    48  // number, similar to git.
    49  //
    50  // Each block directory has the following files:
    51  //
    52  //   - id:   The full block ID in binary format. Always present.
    53  //   - data: The raw block data that should hash to the block ID.
    54  //     May be missing.
    55  //   - ksh:  The raw data for the associated key server half.
    56  //     May be missing, but should be present when data is.
    57  //   - refs: The list of references to the block, along with other
    58  //     block-specific info, encoded as a serialized
    59  //     blockJournalInfo. May be missing.  TODO: rename this to
    60  //     something more generic if we ever upgrade the journal
    61  //     version.
    62  //
    63  // Future versions of the disk store might add more files to this
    64  // directory; if any code is written to move blocks around, it should
    65  // be careful to preserve any unknown files in a block directory.
    66  //
    67  // The maximum number of characters added to the root dir by a block
    68  // disk store is 44:
    69  //
    70  //	/01ff/f...(30 characters total)...ff/data
    71  //
    72  // blockDiskStore is goroutine-safe on a per-operation, per-block ID
    73  // basis, so any code that uses it can make concurrent calls.
    74  // However, it's likely that much of the time, the caller will require
    75  // consistency across multiple calls (i.e., one call to
    76  // `getDataSize()`, followed by a call to `remove()`), so higher-level
    77  // locking is also recommended.  For performance reasons though, it's
    78  // recommended that the `put()` method can be called concurrently, as
    79  // needed.
    80  type blockDiskStore struct {
    81  	codec kbfscodec.Codec
    82  	dir   string
    83  
    84  	lock sync.Mutex
    85  	puts map[kbfsblock.ID]<-chan struct{}
    86  }
    87  
    88  // filesPerBlockMax is an upper bound for the number of files
    89  // (including directories) to store one block: 4 for the regular
    90  // files, 2 for the (splayed) directories, and 1 for the journal
    91  // entry.
    92  const filesPerBlockMax = 7
    93  
    94  // makeBlockDiskStore returns a new blockDiskStore for the given
    95  // directory.
    96  func makeBlockDiskStore(codec kbfscodec.Codec, dir string) *blockDiskStore {
    97  	return &blockDiskStore{
    98  		codec: codec,
    99  		dir:   dir,
   100  		puts:  make(map[kbfsblock.ID]<-chan struct{}),
   101  	}
   102  }
   103  
   104  // The functions below are for building various paths.
   105  
   106  func (s *blockDiskStore) blockPath(id kbfsblock.ID) string {
   107  	// Truncate to 34 characters, which corresponds to 16 random
   108  	// bytes (since the first byte is a hash type) or 128 random
   109  	// bits, which means that the expected number of blocks
   110  	// generated before getting a path collision is 2^64 (see
   111  	// https://en.wikipedia.org/wiki/Birthday_problem#Cast_as_a_collision_problem
   112  	// ).
   113  	idStr := id.String()
   114  	return filepath.Join(s.dir, idStr[:4], idStr[4:34])
   115  }
   116  
   117  func (s *blockDiskStore) dataPath(id kbfsblock.ID) string {
   118  	return filepath.Join(s.blockPath(id), "data")
   119  }
   120  
   121  const idFilename = "id"
   122  
   123  func (s *blockDiskStore) idPath(id kbfsblock.ID) string {
   124  	return filepath.Join(s.blockPath(id), idFilename)
   125  }
   126  
   127  func (s *blockDiskStore) keyServerHalfPath(id kbfsblock.ID) string {
   128  	return filepath.Join(s.blockPath(id), "ksh")
   129  }
   130  
   131  func (s *blockDiskStore) infoPath(id kbfsblock.ID) string {
   132  	// TODO: change the file name to "info" the next we change the
   133  	// journal layout.
   134  	return filepath.Join(s.blockPath(id), "refs")
   135  }
   136  
   137  // makeDir makes the directory for the given block ID and writes the
   138  // ID file, if necessary.
   139  func (s *blockDiskStore) makeDir(id kbfsblock.ID) error {
   140  	err := ioutil.MkdirAll(s.blockPath(id), 0700)
   141  	if err != nil {
   142  		return err
   143  	}
   144  
   145  	// TODO: Only write if the file doesn't exist.
   146  
   147  	return ioutil.WriteFile(s.idPath(id), []byte(id.String()), 0600)
   148  }
   149  
   150  // blockJournalInfo contains info about a particular block in the
   151  // journal, such as the set of references to it.
   152  type blockJournalInfo struct {
   153  	Refs    blockRefMap
   154  	Flushed bool `codec:"f,omitempty"`
   155  
   156  	codec.UnknownFieldSetHandler
   157  }
   158  
   159  // TODO: Add caching for refs
   160  
   161  func (s *blockDiskStore) startOpOrWait(id kbfsblock.ID) (
   162  	closeCh chan<- struct{}, waitCh <-chan struct{}) {
   163  	s.lock.Lock()
   164  	defer s.lock.Unlock()
   165  
   166  	waitCh = s.puts[id]
   167  	if waitCh == nil {
   168  		// If this caller is getting exclusive access to this `id`,
   169  		// make a channel, store it as receive-only in `puts`, and
   170  		// return it as send-only to the caller, to ensure that only
   171  		// this caller can finish the exclusive access.
   172  		ch := make(chan struct{})
   173  		s.puts[id] = ch
   174  		closeCh = ch
   175  	}
   176  
   177  	return closeCh, waitCh
   178  }
   179  
   180  func (s *blockDiskStore) finishOp(id kbfsblock.ID, closeCh chan<- struct{}) {
   181  	s.lock.Lock()
   182  	defer s.lock.Unlock()
   183  	delete(s.puts, id)
   184  	close(closeCh)
   185  }
   186  
   187  func (s *blockDiskStore) exclusify(ctx context.Context, id kbfsblock.ID) (
   188  	cleanupFn func(), err error) {
   189  	// Get a guarantee that we're acting exclusively on this
   190  	// particular block ID.
   191  	for {
   192  		// Repeatedly request exclusive access until until we don't
   193  		// get a non-nil channel to wait on.
   194  		closeCh, waitCh := s.startOpOrWait(id)
   195  		if waitCh == nil {
   196  			// If there's nothing to wait on, then we have exclusive
   197  			// access, so return.
   198  			return func() { s.finishOp(id, closeCh) }, nil
   199  		}
   200  		select {
   201  		case <-waitCh:
   202  		case <-ctx.Done():
   203  			return nil, errors.WithStack(ctx.Err())
   204  		}
   205  	}
   206  }
   207  
   208  // getRefInfo returns the references for the given ID.  exclusify must
   209  // have been called by the caller.
   210  func (s *blockDiskStore) getInfo(id kbfsblock.ID) (blockJournalInfo, error) {
   211  	var info blockJournalInfo
   212  	err := kbfscodec.DeserializeFromFile(s.codec, s.infoPath(id), &info)
   213  	if !ioutil.IsNotExist(err) && err != nil {
   214  		return blockJournalInfo{}, err
   215  	}
   216  
   217  	if info.Refs == nil {
   218  		info.Refs = make(blockRefMap)
   219  	}
   220  
   221  	return info, nil
   222  }
   223  
   224  // putRefInfo stores the given references for the given ID.  exclusify
   225  // must have been called by the caller.
   226  func (s *blockDiskStore) putInfo(
   227  	id kbfsblock.ID, info blockJournalInfo) error {
   228  	return kbfscodec.SerializeToFile(s.codec, info, s.infoPath(id))
   229  }
   230  
   231  // addRefs adds references for the given contexts to the given ID, all
   232  // with the same status and tag.  `exclusify` must be called by the
   233  // caller.
   234  func (s *blockDiskStore) addRefsExclusive(
   235  	id kbfsblock.ID, contexts []kbfsblock.Context, status blockRefStatus,
   236  	tag string) error {
   237  	info, err := s.getInfo(id)
   238  	if err != nil {
   239  		return err
   240  	}
   241  
   242  	if len(info.Refs) > 0 {
   243  		// Check existing contexts, if any.
   244  		for _, context := range contexts {
   245  			_, _, err := info.Refs.checkExists(context)
   246  			if err != nil {
   247  				return err
   248  			}
   249  		}
   250  	}
   251  
   252  	for _, context := range contexts {
   253  		err = info.Refs.put(context, status, tag)
   254  		if err != nil {
   255  			return err
   256  		}
   257  	}
   258  
   259  	return s.putInfo(id, info)
   260  }
   261  
   262  // getData returns the data and server half for the given ID, if
   263  // present.  `exclusify` must be called by the caller.
   264  func (s *blockDiskStore) getDataExclusive(id kbfsblock.ID) (
   265  	[]byte, kbfscrypto.BlockCryptKeyServerHalf, error) {
   266  	data, err := ioutil.ReadFile(s.dataPath(id))
   267  	if ioutil.IsNotExist(err) {
   268  		return nil, kbfscrypto.BlockCryptKeyServerHalf{},
   269  			blockNonExistentError{id}
   270  	} else if err != nil {
   271  		return nil, kbfscrypto.BlockCryptKeyServerHalf{}, err
   272  	}
   273  
   274  	keyServerHalfPath := s.keyServerHalfPath(id)
   275  	buf, err := ioutil.ReadFile(keyServerHalfPath)
   276  	if ioutil.IsNotExist(err) {
   277  		return nil, kbfscrypto.BlockCryptKeyServerHalf{},
   278  			blockNonExistentError{id}
   279  	} else if err != nil {
   280  		return nil, kbfscrypto.BlockCryptKeyServerHalf{}, err
   281  	}
   282  
   283  	// Check integrity.
   284  
   285  	err = verifyLocalBlockIDMaybe(data, id)
   286  	if err != nil {
   287  		return nil, kbfscrypto.BlockCryptKeyServerHalf{}, err
   288  	}
   289  
   290  	var serverHalf kbfscrypto.BlockCryptKeyServerHalf
   291  	err = serverHalf.UnmarshalBinary(buf)
   292  	if err != nil {
   293  		return nil, kbfscrypto.BlockCryptKeyServerHalf{}, err
   294  	}
   295  
   296  	return data, serverHalf, nil
   297  }
   298  
   299  // getData returns the data and server half for the given ID, if
   300  // present.
   301  func (s *blockDiskStore) getData(ctx context.Context, id kbfsblock.ID) (
   302  	[]byte, kbfscrypto.BlockCryptKeyServerHalf, error) {
   303  	cleanup, err := s.exclusify(ctx, id)
   304  	if err != nil {
   305  		return nil, kbfscrypto.BlockCryptKeyServerHalf{}, err
   306  	}
   307  	defer cleanup()
   308  
   309  	return s.getDataExclusive(id)
   310  }
   311  
   312  // All functions below are public functions.
   313  
   314  func (s *blockDiskStore) hasAnyRefExclusive(
   315  	id kbfsblock.ID) (bool, error) {
   316  	info, err := s.getInfo(id)
   317  	if err != nil {
   318  		return false, err
   319  	}
   320  
   321  	return len(info.Refs) > 0, nil
   322  }
   323  
   324  func (s *blockDiskStore) hasAnyRef(
   325  	ctx context.Context, id kbfsblock.ID) (bool, error) {
   326  	cleanup, err := s.exclusify(ctx, id)
   327  	if err != nil {
   328  		return false, err
   329  	}
   330  	defer cleanup()
   331  
   332  	return s.hasAnyRefExclusive(id)
   333  }
   334  
   335  func (s *blockDiskStore) hasNonArchivedRef(
   336  	ctx context.Context, id kbfsblock.ID) (bool, error) {
   337  	cleanup, err := s.exclusify(ctx, id)
   338  	if err != nil {
   339  		return false, err
   340  	}
   341  	defer cleanup()
   342  
   343  	info, err := s.getInfo(id)
   344  	if err != nil {
   345  		return false, err
   346  	}
   347  
   348  	return info.Refs.hasNonArchivedRef(), nil
   349  }
   350  
   351  func (s *blockDiskStore) getLiveCount(
   352  	ctx context.Context, id kbfsblock.ID) (int, error) {
   353  	cleanup, err := s.exclusify(ctx, id)
   354  	if err != nil {
   355  		return 0, err
   356  	}
   357  	defer cleanup()
   358  
   359  	info, err := s.getInfo(id)
   360  	if err != nil {
   361  		return 0, err
   362  	}
   363  
   364  	return info.Refs.getLiveCount(), nil
   365  }
   366  
   367  func (s *blockDiskStore) hasContextExclusive(
   368  	id kbfsblock.ID, context kbfsblock.Context) (
   369  	bool, blockRefStatus, error) {
   370  	info, err := s.getInfo(id)
   371  	if err != nil {
   372  		return false, unknownBlockRef, err
   373  	}
   374  
   375  	return info.Refs.checkExists(context)
   376  }
   377  
   378  func (s *blockDiskStore) hasContext(
   379  	ctx context.Context, id kbfsblock.ID, context kbfsblock.Context) (
   380  	bool, blockRefStatus, error) {
   381  	cleanup, err := s.exclusify(ctx, id)
   382  	if err != nil {
   383  		return false, unknownBlockRef, err
   384  	}
   385  	defer cleanup()
   386  
   387  	return s.hasContextExclusive(id, context)
   388  }
   389  
   390  func (s *blockDiskStore) hasDataExclusive(id kbfsblock.ID) (bool, error) {
   391  	_, err := ioutil.Stat(s.dataPath(id))
   392  	if ioutil.IsNotExist(err) {
   393  		return false, nil
   394  	} else if err != nil {
   395  		return false, err
   396  	}
   397  	return true, nil
   398  }
   399  
   400  func (s *blockDiskStore) hasData(
   401  	ctx context.Context, id kbfsblock.ID) (bool, error) {
   402  	cleanup, err := s.exclusify(ctx, id)
   403  	if err != nil {
   404  		return false, err
   405  	}
   406  	defer cleanup()
   407  
   408  	_, err = ioutil.Stat(s.dataPath(id))
   409  	if ioutil.IsNotExist(err) {
   410  		return false, nil
   411  	} else if err != nil {
   412  		return false, err
   413  	}
   414  	return true, nil
   415  }
   416  
   417  func (s *blockDiskStore) isUnflushed(
   418  	ctx context.Context, id kbfsblock.ID) (bool, error) {
   419  	cleanup, err := s.exclusify(ctx, id)
   420  	if err != nil {
   421  		return false, err
   422  	}
   423  	defer cleanup()
   424  
   425  	ok, err := s.hasDataExclusive(id)
   426  	if err != nil {
   427  		return false, err
   428  	}
   429  
   430  	if !ok {
   431  		return false, nil
   432  	}
   433  
   434  	// The data is there; has it been flushed?
   435  	info, err := s.getInfo(id)
   436  	if err != nil {
   437  		return false, err
   438  	}
   439  
   440  	return !info.Flushed, nil
   441  }
   442  
   443  func (s *blockDiskStore) markFlushed(
   444  	ctx context.Context, id kbfsblock.ID) error {
   445  	cleanup, err := s.exclusify(ctx, id)
   446  	if err != nil {
   447  		return err
   448  	}
   449  	defer cleanup()
   450  
   451  	info, err := s.getInfo(id)
   452  	if err != nil {
   453  		return err
   454  	}
   455  
   456  	info.Flushed = true
   457  	return s.putInfo(id, info)
   458  }
   459  
   460  func (s *blockDiskStore) getDataSize(
   461  	ctx context.Context, id kbfsblock.ID) (int64, error) {
   462  	cleanup, err := s.exclusify(ctx, id)
   463  	if err != nil {
   464  		return 0, err
   465  	}
   466  	defer cleanup()
   467  
   468  	fi, err := ioutil.Stat(s.dataPath(id))
   469  	if ioutil.IsNotExist(err) {
   470  		return 0, nil
   471  	} else if err != nil {
   472  		return 0, err
   473  	}
   474  	return fi.Size(), nil
   475  }
   476  
   477  func (s *blockDiskStore) getDataWithContextExclusive(
   478  	id kbfsblock.ID, context kbfsblock.Context) (
   479  	[]byte, kbfscrypto.BlockCryptKeyServerHalf, error) {
   480  	hasContext, _, err := s.hasContextExclusive(id, context)
   481  	if err != nil {
   482  		return nil, kbfscrypto.BlockCryptKeyServerHalf{}, err
   483  	}
   484  	if !hasContext {
   485  		return nil, kbfscrypto.BlockCryptKeyServerHalf{},
   486  			blockNonExistentError{id}
   487  	}
   488  
   489  	return s.getDataExclusive(id)
   490  }
   491  
   492  func (s *blockDiskStore) getDataWithContext(
   493  	ctx context.Context, id kbfsblock.ID, context kbfsblock.Context) (
   494  	[]byte, kbfscrypto.BlockCryptKeyServerHalf, error) {
   495  	cleanup, err := s.exclusify(ctx, id)
   496  	if err != nil {
   497  		return nil, kbfscrypto.BlockCryptKeyServerHalf{}, err
   498  	}
   499  	defer cleanup()
   500  
   501  	return s.getDataWithContextExclusive(id, context)
   502  }
   503  
   504  func (s *blockDiskStore) getAllRefsForTest() (map[kbfsblock.ID]blockRefMap, error) {
   505  	res := make(map[kbfsblock.ID]blockRefMap)
   506  
   507  	fileInfos, err := ioutil.ReadDir(s.dir)
   508  	if ioutil.IsNotExist(err) {
   509  		return res, nil
   510  	} else if err != nil {
   511  		return nil, err
   512  	}
   513  
   514  	for _, fi := range fileInfos {
   515  		name := fi.Name()
   516  		if !fi.IsDir() {
   517  			return nil, errors.Errorf("Unexpected non-dir %q", name)
   518  		}
   519  
   520  		subFileInfos, err := ioutil.ReadDir(filepath.Join(s.dir, name))
   521  		if err != nil {
   522  			return nil, err
   523  		}
   524  
   525  		for _, sfi := range subFileInfos {
   526  			subName := sfi.Name()
   527  			if !sfi.IsDir() {
   528  				return nil, errors.Errorf("Unexpected non-dir %q",
   529  					subName)
   530  			}
   531  
   532  			idPath := filepath.Join(
   533  				s.dir, name, subName, idFilename)
   534  			idBytes, err := ioutil.ReadFile(idPath)
   535  			if err != nil {
   536  				return nil, err
   537  			}
   538  
   539  			id, err := kbfsblock.IDFromString(string(idBytes))
   540  			if err != nil {
   541  				return nil, errors.WithStack(err)
   542  			}
   543  
   544  			if !strings.HasPrefix(id.String(), name+subName) {
   545  				return nil, errors.Errorf(
   546  					"%q unexpectedly not a prefix of %q",
   547  					name+subName, id.String())
   548  			}
   549  
   550  			info, err := s.getInfo(id)
   551  			if err != nil {
   552  				return nil, err
   553  			}
   554  
   555  			if len(info.Refs) > 0 {
   556  				res[id] = info.Refs
   557  			}
   558  		}
   559  	}
   560  
   561  	return res, nil
   562  }
   563  
   564  // put puts the given data for the block, which may already exist, and
   565  // adds a reference for the given context. If isRegularPut is true,
   566  // additional validity checks are performed.  If err is nil, putData
   567  // indicates whether the data didn't already exist and was put; if
   568  // false, it means that the data already exists, but this might have
   569  // added a new ref.
   570  //
   571  // For performance reasons, this method can be called concurrently by
   572  // many goroutines for different blocks.
   573  //
   574  // Note that the block won't be get-able until `addReference`
   575  // explicitly adds a tag for it.
   576  func (s *blockDiskStore) put(
   577  	ctx context.Context, isRegularPut bool, id kbfsblock.ID,
   578  	context kbfsblock.Context, buf []byte,
   579  	serverHalf kbfscrypto.BlockCryptKeyServerHalf) (
   580  	putData bool, err error) {
   581  	cleanup, err := s.exclusify(ctx, id)
   582  	if err != nil {
   583  		return false, err
   584  	}
   585  	defer cleanup()
   586  
   587  	err = validateBlockPut(isRegularPut, id, context, buf)
   588  	if err != nil {
   589  		return false, err
   590  	}
   591  
   592  	// Check the data and retrieve the server half, if they exist.
   593  	_, existingServerHalf, err := s.getDataWithContextExclusive(id, context)
   594  	var exists bool
   595  	switch err.(type) {
   596  	case blockNonExistentError:
   597  		exists = false
   598  	case nil:
   599  		exists = true
   600  	default:
   601  		return false, err
   602  	}
   603  
   604  	if exists {
   605  		// If the entry already exists, everything should be
   606  		// the same, except for possibly additional
   607  		// references.
   608  
   609  		// We checked that both buf and the existing data hash
   610  		// to id, so no need to check that they're both equal.
   611  
   612  		if isRegularPut && existingServerHalf != serverHalf {
   613  			return false, errors.Errorf(
   614  				"key server half mismatch: expected %s, got %s",
   615  				existingServerHalf, serverHalf)
   616  		}
   617  	} else {
   618  		err = s.makeDir(id)
   619  		if err != nil {
   620  			return false, err
   621  		}
   622  
   623  		err = ioutil.WriteFile(s.dataPath(id), buf, 0600)
   624  		if err != nil {
   625  			return false, err
   626  		}
   627  
   628  		// TODO: Add integrity-checking for key server half?
   629  
   630  		data, err := serverHalf.MarshalBinary()
   631  		if err != nil {
   632  			return false, err
   633  		}
   634  		err = ioutil.WriteFile(s.keyServerHalfPath(id), data, 0600)
   635  		if err != nil {
   636  			return false, err
   637  		}
   638  	}
   639  
   640  	return !exists, nil
   641  }
   642  
   643  func (s *blockDiskStore) addReference(
   644  	ctx context.Context, id kbfsblock.ID, context kbfsblock.Context,
   645  	tag string) error {
   646  	cleanup, err := s.exclusify(ctx, id)
   647  	if err != nil {
   648  		return err
   649  	}
   650  	defer cleanup()
   651  
   652  	err = s.makeDir(id)
   653  	if err != nil {
   654  		return err
   655  	}
   656  
   657  	return s.addRefsExclusive(
   658  		id, []kbfsblock.Context{context}, liveBlockRef, tag)
   659  }
   660  
   661  func (s *blockDiskStore) archiveReference(
   662  	ctx context.Context, id kbfsblock.ID, idContexts []kbfsblock.Context,
   663  	tag string) error {
   664  	cleanup, err := s.exclusify(ctx, id)
   665  	if err != nil {
   666  		return err
   667  	}
   668  	defer cleanup()
   669  
   670  	err = s.makeDir(id)
   671  	if err != nil {
   672  		return err
   673  	}
   674  
   675  	return s.addRefsExclusive(id, idContexts, archivedBlockRef, tag)
   676  }
   677  
   678  func (s *blockDiskStore) archiveReferences(
   679  	ctx context.Context, contexts kbfsblock.ContextMap, tag string) error {
   680  	for id, idContexts := range contexts {
   681  		err := s.archiveReference(ctx, id, idContexts, tag)
   682  		if err != nil {
   683  			return err
   684  		}
   685  	}
   686  
   687  	return nil
   688  }
   689  
   690  // removeReferences removes references for the given contexts from
   691  // their respective IDs. If tag is non-empty, then a reference will be
   692  // removed only if its most recent tag (passed in to addRefs) matches
   693  // the given one.
   694  func (s *blockDiskStore) removeReferences(
   695  	ctx context.Context, id kbfsblock.ID, contexts []kbfsblock.Context,
   696  	tag string) (liveCount int, err error) {
   697  	cleanup, err := s.exclusify(ctx, id)
   698  	if err != nil {
   699  		return 0, err
   700  	}
   701  	defer cleanup()
   702  
   703  	info, err := s.getInfo(id)
   704  	if err != nil {
   705  		return 0, err
   706  	}
   707  	if len(info.Refs) == 0 {
   708  		return 0, nil
   709  	}
   710  
   711  	for _, context := range contexts {
   712  		err := info.Refs.remove(context, tag)
   713  		if err != nil {
   714  			return 0, err
   715  		}
   716  		if len(info.Refs) == 0 {
   717  			break
   718  		}
   719  	}
   720  
   721  	err = s.putInfo(id, info)
   722  	if err != nil {
   723  		return 0, err
   724  	}
   725  
   726  	return len(info.Refs), nil
   727  }
   728  
   729  // remove removes any existing data for the given ID, which must not
   730  // have any references left.
   731  func (s *blockDiskStore) remove(ctx context.Context, id kbfsblock.ID) error {
   732  	cleanup, err := s.exclusify(ctx, id)
   733  	if err != nil {
   734  		return err
   735  	}
   736  	defer cleanup()
   737  
   738  	hasAnyRef, err := s.hasAnyRefExclusive(id)
   739  	if err != nil {
   740  		return err
   741  	}
   742  	if hasAnyRef {
   743  		return errors.Errorf(
   744  			"Trying to remove data for referenced block %s", id)
   745  	}
   746  	path := s.blockPath(id)
   747  
   748  	err = ioutil.RemoveAll(path)
   749  	if err != nil {
   750  		return err
   751  	}
   752  
   753  	// Remove the parent (splayed) directory if it exists and is
   754  	// empty.
   755  	err = ioutil.Remove(filepath.Dir(path))
   756  	if ioutil.IsNotExist(err) || ioutil.IsExist(err) {
   757  		err = nil
   758  	}
   759  	return err
   760  }
   761  
   762  func (s *blockDiskStore) clear() error {
   763  	return ioutil.RemoveAll(s.dir)
   764  }