github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/md_id_journal.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  	"reflect"
     9  
    10  	"github.com/keybase/client/go/kbfs/ioutil"
    11  	"github.com/keybase/client/go/kbfs/kbfscodec"
    12  	"github.com/keybase/client/go/kbfs/kbfsmd"
    13  	"github.com/keybase/go-codec/codec"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  // An mdIDJournal wraps a diskJournal to provide a persistent list of
    18  // MdIDs (with possible other fields in the future) with sequential
    19  // Revisions for a single branch.
    20  //
    21  // Like diskJournal, this type assumes that the directory passed into
    22  // makeMdIDJournal isn't used by anything else, and that all
    23  // synchronization is done at a higher level.
    24  //
    25  // TODO: Write unit tests for this. For now, we're relying on
    26  // md_journal.go's unit tests.
    27  type mdIDJournal struct {
    28  	j *diskJournal
    29  }
    30  
    31  // An mdIDJournalEntry is an MdID and a boolean describing whether
    32  // this entry was the result of a local squash. Make sure that new
    33  // fields don't depend on the ID or `isLocalSquash`, as the mdJournal
    34  // may change these when converting to a branch.  Note that
    35  // `isLocalSquash` may only be true for entries in a continuous prefix
    36  // of the id journal; once there is one entry with `isLocalSquash =
    37  // false`, it will be the same in all the remaining entries.
    38  type mdIDJournalEntry struct {
    39  	ID kbfsmd.ID
    40  	// IsLocalSquash is true when this MD is the result of
    41  	// squashing some other local MDs.
    42  	IsLocalSquash bool `codec:",omitempty"`
    43  	// WKBNew is true when the writer key bundle for this MD is
    44  	// new and has to be pushed to the server. This is always
    45  	// false for MDv2.
    46  	WKBNew bool `codec:",omitempty"`
    47  	// RKBNew is true when the reader key bundle for this MD is
    48  	// new and has to be pushed to the server. This is always
    49  	// false for MDv2.
    50  	RKBNew bool `codec:",omitempty"`
    51  
    52  	codec.UnknownFieldSetHandler
    53  }
    54  
    55  func makeMdIDJournal(codec kbfscodec.Codec, dir string) (mdIDJournal, error) {
    56  	j, err :=
    57  		makeDiskJournal(codec, dir, reflect.TypeOf(mdIDJournalEntry{}))
    58  	if err != nil {
    59  		return mdIDJournal{}, err
    60  	}
    61  	return mdIDJournal{j}, nil
    62  }
    63  
    64  func ordinalToRevision(o journalOrdinal) (kbfsmd.Revision, error) {
    65  	r := kbfsmd.Revision(o)
    66  	if r < kbfsmd.RevisionInitial {
    67  		return kbfsmd.RevisionUninitialized, errors.Errorf(
    68  			"Cannot convert ordinal %s to a kbfsmd.Revision", o)
    69  	}
    70  	return r, nil
    71  }
    72  
    73  func revisionToOrdinal(r kbfsmd.Revision) (journalOrdinal, error) {
    74  	if r < kbfsmd.RevisionInitial {
    75  		return journalOrdinal(0), errors.Errorf(
    76  			"Cannot convert revision %s to an ordinal", r)
    77  	}
    78  	return journalOrdinal(r), nil
    79  }
    80  
    81  // TODO: Consider caching the values returned by the read functions
    82  // below in memory.
    83  
    84  func (j mdIDJournal) readEarliestRevision() (kbfsmd.Revision, error) {
    85  	o, err := j.j.readEarliestOrdinal()
    86  	if ioutil.IsNotExist(err) {
    87  		return kbfsmd.RevisionUninitialized, nil
    88  	} else if err != nil {
    89  		return kbfsmd.RevisionUninitialized, err
    90  	}
    91  	return ordinalToRevision(o)
    92  }
    93  
    94  func (j mdIDJournal) readLatestRevision() (kbfsmd.Revision, error) {
    95  	o, err := j.j.readLatestOrdinal()
    96  	if ioutil.IsNotExist(err) {
    97  		return kbfsmd.RevisionUninitialized, nil
    98  	} else if err != nil {
    99  		return kbfsmd.RevisionUninitialized, err
   100  	}
   101  	return ordinalToRevision(o)
   102  }
   103  
   104  func (j mdIDJournal) writeLatestRevision(r kbfsmd.Revision) error {
   105  	o, err := revisionToOrdinal(r)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	return j.j.writeLatestOrdinal(o)
   110  }
   111  
   112  func (j mdIDJournal) readJournalEntry(r kbfsmd.Revision) (
   113  	mdIDJournalEntry, error) {
   114  	o, err := revisionToOrdinal(r)
   115  	if err != nil {
   116  		return mdIDJournalEntry{}, err
   117  	}
   118  	e, err := j.j.readJournalEntry(o)
   119  	if err != nil {
   120  		return mdIDJournalEntry{}, err
   121  	}
   122  
   123  	return e.(mdIDJournalEntry), nil
   124  }
   125  
   126  // All functions below are public functions.
   127  
   128  func (j mdIDJournal) length() uint64 {
   129  	return j.j.length()
   130  }
   131  
   132  func (j mdIDJournal) end() (kbfsmd.Revision, error) {
   133  	last, err := j.readLatestRevision()
   134  	if err != nil {
   135  		return kbfsmd.RevisionUninitialized, err
   136  	}
   137  	if last == kbfsmd.RevisionUninitialized {
   138  		return kbfsmd.RevisionUninitialized, nil
   139  	}
   140  
   141  	return last + 1, nil
   142  }
   143  
   144  func (j mdIDJournal) getEarliestEntry() (
   145  	entry mdIDJournalEntry, exists bool, err error) {
   146  	earliestRevision, err := j.readEarliestRevision()
   147  	if err != nil {
   148  		return mdIDJournalEntry{}, false, err
   149  	} else if earliestRevision == kbfsmd.RevisionUninitialized {
   150  		return mdIDJournalEntry{}, false, nil
   151  	}
   152  	entry, err = j.readJournalEntry(earliestRevision)
   153  	if err != nil {
   154  		return mdIDJournalEntry{}, false, err
   155  	}
   156  	return entry, true, err
   157  }
   158  
   159  func (j mdIDJournal) getLatestEntry() (
   160  	entry mdIDJournalEntry, exists bool, err error) {
   161  	latestRevision, err := j.readLatestRevision()
   162  	if err != nil {
   163  		return mdIDJournalEntry{}, false, err
   164  	} else if latestRevision == kbfsmd.RevisionUninitialized {
   165  		return mdIDJournalEntry{}, false, nil
   166  	}
   167  	entry, err = j.readJournalEntry(latestRevision)
   168  	if err != nil {
   169  		return mdIDJournalEntry{}, false, err
   170  	}
   171  	return entry, true, err
   172  }
   173  
   174  func (j mdIDJournal) getEntryRange(start, stop kbfsmd.Revision) (
   175  	kbfsmd.Revision, []mdIDJournalEntry, error) {
   176  	earliestRevision, err := j.readEarliestRevision()
   177  	if err != nil {
   178  		return kbfsmd.RevisionUninitialized, nil, err
   179  	} else if earliestRevision == kbfsmd.RevisionUninitialized {
   180  		return kbfsmd.RevisionUninitialized, nil, nil
   181  	}
   182  
   183  	latestRevision, err := j.readLatestRevision()
   184  	if err != nil {
   185  		return kbfsmd.RevisionUninitialized, nil, err
   186  	} else if latestRevision == kbfsmd.RevisionUninitialized {
   187  		return kbfsmd.RevisionUninitialized, nil, nil
   188  	}
   189  
   190  	if start < earliestRevision {
   191  		start = earliestRevision
   192  	}
   193  
   194  	if stop > latestRevision {
   195  		stop = latestRevision
   196  	}
   197  
   198  	if stop < start {
   199  		return kbfsmd.RevisionUninitialized, nil, nil
   200  	}
   201  
   202  	var entries []mdIDJournalEntry
   203  	for i := start; i <= stop; i++ {
   204  		entry, err := j.readJournalEntry(i)
   205  		if err != nil {
   206  			return kbfsmd.RevisionUninitialized, nil, err
   207  		}
   208  		entries = append(entries, entry)
   209  	}
   210  	return start, entries, nil
   211  }
   212  
   213  func (j mdIDJournal) replaceHead(entry mdIDJournalEntry) error {
   214  	o, err := j.j.readLatestOrdinal()
   215  	if err != nil {
   216  		return err
   217  	}
   218  	return j.j.writeJournalEntry(o, entry)
   219  }
   220  
   221  func (j mdIDJournal) append(r kbfsmd.Revision, entry mdIDJournalEntry) error {
   222  	o, err := revisionToOrdinal(r)
   223  	if err != nil {
   224  		return err
   225  	}
   226  	_, err = j.j.appendJournalEntry(&o, entry)
   227  	return err
   228  }
   229  
   230  func (j mdIDJournal) removeEarliest() (empty bool, err error) {
   231  	return j.j.removeEarliest()
   232  }
   233  
   234  func (j mdIDJournal) clear() error {
   235  	return j.j.clear()
   236  }
   237  
   238  func (j mdIDJournal) clearFrom(revision kbfsmd.Revision) error {
   239  	earliestRevision, err := j.readEarliestRevision()
   240  	if err != nil {
   241  		return err
   242  	}
   243  
   244  	if revision < earliestRevision {
   245  		return errors.Errorf("Cannot call clearFrom with revision %s < %s",
   246  			revision, earliestRevision)
   247  	}
   248  
   249  	if revision == earliestRevision {
   250  		return j.clear()
   251  	}
   252  
   253  	latestRevision, err := j.readLatestRevision()
   254  	if err != nil {
   255  		return err
   256  	}
   257  
   258  	err = j.writeLatestRevision(revision - 1)
   259  	if err != nil {
   260  		return err
   261  	}
   262  
   263  	o, err := revisionToOrdinal(revision)
   264  	if err != nil {
   265  		return err
   266  	}
   267  
   268  	latestOrdinal, err := revisionToOrdinal(latestRevision)
   269  	if err != nil {
   270  		return err
   271  	}
   272  
   273  	for ; o <= latestOrdinal; o++ {
   274  		p := j.j.journalEntryPath(o)
   275  		err = ioutil.Remove(p)
   276  		if err != nil {
   277  			return err
   278  		}
   279  	}
   280  
   281  	return nil
   282  }
   283  
   284  // Note that since diskJournal.move takes a pointer receiver, so must
   285  // this.
   286  func (j *mdIDJournal) move(newDir string) (oldDir string, err error) {
   287  	return j.j.move(newDir)
   288  }