github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libkbfs/md_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 "path/filepath" 9 "time" 10 11 "github.com/keybase/client/go/kbfs/data" 12 "github.com/keybase/client/go/kbfs/idutil" 13 "github.com/keybase/client/go/kbfs/ioutil" 14 "github.com/keybase/client/go/kbfs/kbfscodec" 15 "github.com/keybase/client/go/kbfs/kbfscrypto" 16 "github.com/keybase/client/go/kbfs/kbfsmd" 17 "github.com/keybase/client/go/kbfs/tlf" 18 "github.com/keybase/client/go/logger" 19 "github.com/keybase/client/go/protocol/keybase1" 20 "github.com/pkg/errors" 21 "golang.org/x/net/context" 22 ) 23 24 // ImmutableBareRootMetadata is a thin wrapper around a 25 // BareRootMetadata and a kbfsmd.ExtraMetadata that takes ownership of it 26 // and does not ever modify it again. Thus, its MdID can be calculated 27 // and stored along with a local timestamp. ImmutableBareRootMetadata 28 // objects can be assumed to never alias a (modifiable) 29 // BareRootMetadata. 30 // 31 // Note that kbfsmd.MakeID() on an ImmutableBareRootMetadata will 32 // compute the wrong result, since anonymous fields of interface type 33 // are not encoded inline by the codec. Use 34 // kbfsmd.MakeID(ibrmd.BareRootMetadata) instead. 35 // 36 // TODO: Move this to bare_root_metadata.go if it's used in more 37 // places. 38 type ImmutableBareRootMetadata struct { 39 kbfsmd.RootMetadata 40 extra kbfsmd.ExtraMetadata 41 mdID kbfsmd.ID 42 localTimestamp time.Time 43 } 44 45 // MakeImmutableBareRootMetadata makes a new ImmutableBareRootMetadata 46 // from the given BareRootMetadata and its corresponding MdID. 47 func MakeImmutableBareRootMetadata( 48 rmd kbfsmd.RootMetadata, extra kbfsmd.ExtraMetadata, mdID kbfsmd.ID, 49 localTimestamp time.Time) ImmutableBareRootMetadata { 50 if mdID == (kbfsmd.ID{}) { 51 panic("zero mdID passed to MakeImmutableBareRootMetadata") 52 } 53 return ImmutableBareRootMetadata{rmd, extra, mdID, localTimestamp} 54 } 55 56 // MakeBareTlfHandleWithExtra makes a BareTlfHandle for this 57 // ImmutableBareRootMetadata. Should be used only by servers and MDOps. 58 func (ibrmd ImmutableBareRootMetadata) MakeBareTlfHandleWithExtra() ( 59 tlf.Handle, error) { 60 return ibrmd.RootMetadata.MakeBareTlfHandle(ibrmd.extra) 61 } 62 63 // mdJournal stores a single ordered list of metadata IDs for a (TLF, 64 // user, device) tuple, along with the associated metadata objects, in 65 // flat files on disk in a directory. The directory may be shared with 66 // other things, but it is assumed that any subdirectories created by 67 // mdJournal is not used by anything else. 68 // 69 // The directory layout looks like: 70 // 71 // dir/md_journal/EARLIEST 72 // dir/md_journal/LATEST 73 // dir/md_journal/0...001 74 // dir/md_journal/0...002 75 // dir/md_journal/0...fff 76 // dir/mds/0100/0...01/data 77 // dir/mds/0100/0...01/info.json 78 // ... 79 // dir/mds/01ff/f...ff/data 80 // dir/mds/01ff/f...ff/info.json 81 // dir/wkbv3/0100...01 82 // ... 83 // dir/wkbv3/0100...ff 84 // dir/rkbv3/0100...01 85 // ... 86 // dir/rkbv3/0100...ff 87 // 88 // There's a single journal subdirectory; the journal ordinals are 89 // just Revisions, and the journal entries are just MdIDs. 90 // 91 // The Metadata objects are stored separately in dir/mds. Each MD has 92 // its own subdirectory with its ID truncated to 17 bytes (34 93 // characters) as a name. The MD subdirectories are splayed over (# of 94 // possible hash types) * 256 subdirectories -- one byte for the hash 95 // type (currently only one) plus the first byte of the hash data -- 96 // using the first four characters of the name to keep the number of 97 // directories in dir itself to a manageable number, similar to git. 98 // Each block directory has data, which is the raw MD data that should 99 // hash to the MD ID, and info.json, which contains the version and 100 // timestamp info for that MD. Future versions of the journal might 101 // add more files to this directory; if any code is written to move 102 // MDs around, it should be careful to preserve any unknown files in 103 // an MD directory. 104 // 105 // Writer (reader) key bundles for V3 metadata objects are stored 106 // separately in dir/wkbv3 (dir/rkbv3). The number of bundles is 107 // small, so no need to splay them. 108 // 109 // TODO: Garbage-collect unreferenced key bundles. 110 // 111 // The maximum number of characters added to the root dir by an MD 112 // journal is 50: 113 // 114 // /mds/01ff/f...(30 characters total)...ff/info.json 115 // 116 // This covers even the temporary files created in convertToBranch and 117 // resolveAndClear, which create paths like 118 // 119 // /md_journal123456789/0...(16 characters total)...001 120 // 121 // which have only 37 characters. 122 // 123 // mdJournal is not goroutine-safe, so any code that uses it must 124 // guarantee that only one goroutine at a time calls its functions. 125 type mdJournal struct { 126 // key is assumed to be the VerifyingKey of a device owned by 127 // uid, and both uid and key are assumed constant for the 128 // lifetime of this object. 129 uid keybase1.UID 130 key kbfscrypto.VerifyingKey 131 132 codec kbfscodec.Codec 133 crypto cryptoPure 134 clock Clock 135 teamMemChecker kbfsmd.TeamMembershipChecker 136 osg idutil.OfflineStatusGetter 137 tlfID tlf.ID 138 mdVer kbfsmd.MetadataVer 139 dir string 140 overrideTlfID tlf.ID 141 142 log logger.Logger 143 deferLog logger.Logger 144 145 j mdIDJournal 146 147 // branchID is the kbfsmd.BranchID that every MD in the journal is set 148 // to, except for when it is kbfsmd.PendingLocalSquashBranchID, in which 149 // case the journal is a bunch of MDs with a null branchID 150 // followed by a bunch of MDs with bid = 151 // kbfsmd.PendingLocalSquashBranchID. 152 // 153 // branchID doesn't need to be persisted, even if the journal 154 // becomes empty, since on a restart the branch ID is retrieved 155 // from the server (via GetUnmergedForTLF). 156 branchID kbfsmd.BranchID 157 158 // Set only when the journal becomes empty due to 159 // flushing. This doesn't need to be persisted for the same 160 // reason as branchID. 161 lastMdID kbfsmd.ID 162 163 // journalID is a unique identifier for this journal since the 164 // last time, renewed the last time it was completely cleared. It 165 // must be persisted in a file, since it is referenced 166 // persistently in the block journal. It's used to help clear md 167 // markers in the block journal atomically. 168 journalID kbfsmd.ID 169 } 170 171 func makeMDJournalWithIDJournal( 172 ctx context.Context, uid keybase1.UID, key kbfscrypto.VerifyingKey, 173 codec kbfscodec.Codec, crypto cryptoPure, clock Clock, 174 teamMemChecker kbfsmd.TeamMembershipChecker, osg idutil.OfflineStatusGetter, 175 tlfID tlf.ID, mdVer kbfsmd.MetadataVer, dir string, idJournal mdIDJournal, 176 log logger.Logger, overrideTlfID tlf.ID) (*mdJournal, error) { 177 if uid == keybase1.UID("") { 178 return nil, errors.New("Empty user") 179 } 180 if key == (kbfscrypto.VerifyingKey{}) { 181 return nil, errors.New("Empty verifying key") 182 } 183 184 deferLog := log.CloneWithAddedDepth(1) 185 journal := mdJournal{ 186 uid: uid, 187 key: key, 188 codec: codec, 189 crypto: crypto, 190 clock: clock, 191 teamMemChecker: teamMemChecker, 192 osg: osg, 193 tlfID: tlfID, 194 mdVer: mdVer, 195 dir: dir, 196 overrideTlfID: overrideTlfID, 197 log: log, 198 deferLog: deferLog, 199 j: idJournal, 200 } 201 202 _, earliest, _, _, err := journal.getEarliestWithExtra(ctx, false) 203 if err != nil { 204 return nil, err 205 } 206 207 latest, err := journal.getLatest(ctx, false) 208 if err != nil { 209 return nil, err 210 } 211 212 if (earliest == nil) != (latest == ImmutableBareRootMetadata{}) { 213 return nil, errors.Errorf("has earliest=%t != has latest=%t", 214 earliest != nil, 215 latest != ImmutableBareRootMetadata{}) 216 } 217 218 if earliest != nil { 219 if earliest.BID() != latest.BID() && 220 !(earliest.BID() == kbfsmd.NullBranchID && 221 latest.BID() == kbfsmd.PendingLocalSquashBranchID) { 222 return nil, errors.Errorf( 223 "earliest.BID=%s != latest.BID=%s", 224 earliest.BID(), latest.BID()) 225 } 226 log.CDebugf(ctx, "Initializing with branch ID %s", latest.BID()) 227 journal.branchID = latest.BID() 228 } 229 230 return &journal, nil 231 } 232 233 func mdJournalPath(dir string) string { 234 return filepath.Join(dir, "md_journal") 235 } 236 237 func makeMDJournal( 238 ctx context.Context, uid keybase1.UID, key kbfscrypto.VerifyingKey, 239 codec kbfscodec.Codec, crypto cryptoPure, clock Clock, 240 teamMemChecker kbfsmd.TeamMembershipChecker, osg idutil.OfflineStatusGetter, 241 tlfID tlf.ID, mdVer kbfsmd.MetadataVer, dir string, 242 log logger.Logger, overrideTlfID tlf.ID) (*mdJournal, error) { 243 journalDir := mdJournalPath(dir) 244 idJournal, err := makeMdIDJournal(codec, journalDir) 245 if err != nil { 246 return nil, err 247 } 248 return makeMDJournalWithIDJournal( 249 ctx, uid, key, codec, crypto, clock, teamMemChecker, osg, tlfID, mdVer, 250 dir, idJournal, log, overrideTlfID) 251 } 252 253 // The functions below are for building various paths. 254 255 func (j mdJournal) mdsPath() string { 256 return filepath.Join(j.dir, "mds") 257 } 258 259 func (j mdJournal) writerKeyBundlesV3Path() string { 260 return filepath.Join(j.dir, "wkbv3") 261 } 262 263 func (j mdJournal) readerKeyBundlesV3Path() string { 264 return filepath.Join(j.dir, "rkbv3") 265 } 266 267 // The final components of the paths below are truncated to 34 268 // characters, which corresponds to 16 random bytes (since the first 269 // byte is a hash type) or 128 random bits, which means that the 270 // expected number of MDs generated before getting a path collision is 271 // 2^64 (see 272 // https://en.wikipedia.org/wiki/Birthday_problem#Cast_as_a_collision_problem 273 // ). The full ID can be recovered just by hashing the data again with 274 // the same hash type. 275 276 func (j mdJournal) writerKeyBundleV3Path(id kbfsmd.TLFWriterKeyBundleID) string { 277 idStr := id.String() 278 return filepath.Join(j.writerKeyBundlesV3Path(), idStr[:34]) 279 } 280 281 func (j mdJournal) readerKeyBundleV3Path(id kbfsmd.TLFReaderKeyBundleID) string { 282 idStr := id.String() 283 return filepath.Join(j.readerKeyBundlesV3Path(), idStr[:34]) 284 } 285 286 func (j mdJournal) mdJournalDirs() []string { 287 return []string{ 288 mdJournalPath(j.dir), j.mdsPath(), 289 j.writerKeyBundlesV3Path(), j.readerKeyBundlesV3Path(), 290 } 291 } 292 293 func (j mdJournal) mdPath(id kbfsmd.ID) string { 294 idStr := id.String() 295 return filepath.Join(j.mdsPath(), idStr[:4], idStr[4:34]) 296 } 297 298 func (j mdJournal) mdDataPath(id kbfsmd.ID) string { 299 return filepath.Join(j.mdPath(id), "data") 300 } 301 302 func (j mdJournal) mdInfoPath(id kbfsmd.ID) string { 303 return filepath.Join(j.mdPath(id), "info.json") 304 } 305 306 // mdInfo is the structure stored in mdInfoPath(id). 307 // 308 // TODO: Handle unknown fields? We'd have to build a handler for this, 309 // since the Go JSON library doesn't support it natively. 310 type mdInfo struct { 311 Timestamp time.Time 312 Version kbfsmd.MetadataVer 313 } 314 315 func (j mdJournal) getMDInfo(id kbfsmd.ID) (time.Time, kbfsmd.MetadataVer, error) { 316 var info mdInfo 317 err := ioutil.DeserializeFromJSONFile(j.mdInfoPath(id), &info) 318 if err != nil { 319 return time.Time{}, kbfsmd.MetadataVer(-1), err 320 } 321 322 return info.Timestamp, info.Version, nil 323 } 324 325 // putMDInfo assumes that the parent directory of j.mdInfoPath(id) 326 // (which is j.mdPath(id)) has already been created. 327 func (j mdJournal) putMDInfo( 328 id kbfsmd.ID, timestamp time.Time, version kbfsmd.MetadataVer) error { 329 info := mdInfo{timestamp, version} 330 return ioutil.SerializeToJSONFile(info, j.mdInfoPath(id)) 331 } 332 333 // getExtraMetadata gets the extra metadata corresponding to the given 334 // IDs, if any, after checking them. 335 func (j mdJournal) getExtraMetadata( 336 wkbID kbfsmd.TLFWriterKeyBundleID, rkbID kbfsmd.TLFReaderKeyBundleID, 337 wkbNew, rkbNew bool) (kbfsmd.ExtraMetadata, error) { 338 if (wkbID == kbfsmd.TLFWriterKeyBundleID{}) != 339 (rkbID == kbfsmd.TLFReaderKeyBundleID{}) { 340 return nil, errors.Errorf( 341 "wkbID is empty (%t) != rkbID is empty (%t)", 342 wkbID == kbfsmd.TLFWriterKeyBundleID{}, 343 rkbID == kbfsmd.TLFReaderKeyBundleID{}) 344 } 345 346 if wkbID == (kbfsmd.TLFWriterKeyBundleID{}) { 347 return nil, nil 348 } 349 350 wkb, err := kbfsmd.DeserializeTLFWriterKeyBundleV3( 351 j.codec, j.writerKeyBundleV3Path(wkbID)) 352 if err != nil { 353 return nil, err 354 } 355 356 err = kbfsmd.CheckWKBID(j.codec, wkbID, wkb) 357 if err != nil { 358 return nil, err 359 } 360 361 rkb, err := kbfsmd.DeserializeTLFReaderKeyBundleV3( 362 j.codec, j.readerKeyBundleV3Path(rkbID)) 363 if err != nil { 364 return nil, err 365 } 366 367 err = kbfsmd.CheckRKBID(j.codec, rkbID, rkb) 368 if err != nil { 369 return nil, err 370 } 371 372 return kbfsmd.NewExtraMetadataV3(wkb, rkb, wkbNew, rkbNew), nil 373 } 374 375 func (j mdJournal) putExtraMetadata(rmd kbfsmd.RootMetadata, extra kbfsmd.ExtraMetadata) ( 376 wkbNew, rkbNew bool, err error) { 377 wkbID := rmd.GetTLFWriterKeyBundleID() 378 rkbID := rmd.GetTLFReaderKeyBundleID() 379 380 if extra == nil { 381 if wkbID != (kbfsmd.TLFWriterKeyBundleID{}) { 382 panic(errors.Errorf("unexpected non-nil wkbID %s", wkbID)) 383 } 384 if rkbID != (kbfsmd.TLFReaderKeyBundleID{}) { 385 panic(errors.Errorf("unexpected non-nil rkbID %s", rkbID)) 386 } 387 return false, false, nil 388 } 389 390 if wkbID == (kbfsmd.TLFWriterKeyBundleID{}) { 391 panic("writer key bundle ID is empty") 392 } 393 394 if rkbID == (kbfsmd.TLFReaderKeyBundleID{}) { 395 panic("reader key bundle ID is empty") 396 } 397 398 extraV3, ok := extra.(*kbfsmd.ExtraMetadataV3) 399 if !ok { 400 return false, false, errors.New("Invalid extra metadata") 401 } 402 403 // TODO: We lose extraV3.wkbNew and extraV3.rkbNew here. Store 404 // it as part of the mdInfo, so we don't needlessly send it 405 // while flushing. 406 407 err = kbfsmd.CheckWKBID(j.codec, wkbID, extraV3.GetWriterKeyBundle()) 408 if err != nil { 409 return false, false, err 410 } 411 412 err = kbfsmd.CheckRKBID(j.codec, rkbID, extraV3.GetReaderKeyBundle()) 413 if err != nil { 414 return false, false, err 415 } 416 417 err = kbfscodec.SerializeToFileIfNotExist( 418 j.codec, extraV3.GetWriterKeyBundle(), j.writerKeyBundleV3Path(wkbID)) 419 if err != nil { 420 return false, false, err 421 } 422 423 err = kbfscodec.SerializeToFileIfNotExist( 424 j.codec, extraV3.GetReaderKeyBundle(), j.readerKeyBundleV3Path(rkbID)) 425 if err != nil { 426 return false, false, err 427 } 428 429 return extraV3.IsWriterKeyBundleNew(), extraV3.IsReaderKeyBundleNew(), nil 430 } 431 432 // getMDAndExtra verifies the MD data, the writer signature (but not 433 // the key), and the extra metadata for the given ID and returns 434 // them. It also returns the last-modified timestamp of the 435 // file. verifyBranchID should be false only when called from 436 // makeMDJournal, i.e. when figuring out what to set j.branchID in the 437 // first place. 438 // 439 // It returns a kbfsmd.MutableRootMetadata so that it can be put in a 440 // RootMetadataSigned object. 441 func (j mdJournal) getMDAndExtra(ctx context.Context, entry mdIDJournalEntry, 442 verifyBranchID bool) ( 443 kbfsmd.MutableRootMetadata, kbfsmd.ExtraMetadata, time.Time, error) { 444 // Read info. 445 446 timestamp, version, err := j.getMDInfo(entry.ID) 447 if err != nil { 448 return nil, nil, time.Time{}, err 449 } 450 451 // Read data. 452 453 p := j.mdDataPath(entry.ID) 454 data, err := ioutil.ReadFile(p) 455 if err != nil { 456 return nil, nil, time.Time{}, err 457 } 458 459 rmd, err := kbfsmd.DecodeRootMetadata( 460 j.codec, j.tlfID, version, j.mdVer, data) 461 if err != nil { 462 return nil, nil, time.Time{}, err 463 } 464 465 // Check integrity. 466 467 mdID, err := kbfsmd.MakeID(j.codec, rmd) 468 if err != nil { 469 return nil, nil, time.Time{}, err 470 } 471 472 if mdID != entry.ID { 473 return nil, nil, time.Time{}, errors.Errorf( 474 "Metadata ID mismatch: expected %s, got %s", 475 entry.ID, mdID) 476 } 477 478 err = rmd.IsLastModifiedBy(j.uid, j.key) 479 if err != nil { 480 return nil, nil, time.Time{}, err 481 } 482 483 extra, err := j.getExtraMetadata( 484 rmd.GetTLFWriterKeyBundleID(), rmd.GetTLFReaderKeyBundleID(), 485 entry.WKBNew, entry.RKBNew) 486 if err != nil { 487 return nil, nil, time.Time{}, err 488 } 489 490 err = rmd.IsValidAndSigned( 491 ctx, j.codec, j.teamMemChecker, extra, j.key, 492 j.osg.OfflineAvailabilityForID(j.tlfID)) 493 if err != nil { 494 return nil, nil, time.Time{}, err 495 } 496 497 if verifyBranchID && rmd.BID() != j.branchID && 498 !(rmd.BID() == kbfsmd.NullBranchID && j.branchID == kbfsmd.PendingLocalSquashBranchID) { 499 return nil, nil, time.Time{}, errors.Errorf( 500 "Branch ID mismatch: expected %s, got %s", 501 j.branchID, rmd.BID()) 502 } 503 504 // Local conflict branches will have a different local TLF ID. 505 if j.overrideTlfID != tlf.NullID { 506 rmd.SetTlfID(j.overrideTlfID) 507 } 508 509 return rmd, extra, timestamp, nil 510 } 511 512 type mdJournalInfo struct { 513 ID kbfsmd.ID 514 } 515 516 func (j mdJournal) journalInfoPath() string { 517 return filepath.Join(j.j.j.dir, "info") 518 } 519 520 // getOrCreateJournalID returns the unique ID of the journal, renewed 521 // the last time it was cleared. 522 func (j *mdJournal) getOrCreateJournalID() (kbfsmd.ID, error) { 523 if j.journalID.IsValid() { 524 return j.journalID, nil 525 } 526 527 // Read it from the file, if the file exists. 528 p := j.journalInfoPath() 529 var info mdJournalInfo 530 err := kbfscodec.DeserializeFromFile(j.codec, p, &info) 531 switch { 532 case err == nil: 533 j.journalID = info.ID 534 return info.ID, nil 535 case ioutil.IsNotExist(errors.Cause(err)): 536 // Continue. 537 default: 538 return kbfsmd.ID{}, err 539 } 540 541 // Read latest entry ID and serialize it into the info file. We 542 // use the latest entry, rather than the earliest, because when 543 // resolving branches sometimes the earliest entries (local 544 // squashes) are preserved. It doesn't really matter which ID we 545 // pick as long as it is unique after a branch resolution/clear, 546 // and persisted across restarts. 547 entry, exists, err := j.j.getLatestEntry() 548 if err != nil { 549 return kbfsmd.ID{}, err 550 } 551 if !exists { 552 // No journal ID yet. 553 return kbfsmd.ID{}, nil 554 } 555 info.ID = entry.ID 556 err = kbfscodec.SerializeToFile(j.codec, info, p) 557 if err != nil { 558 return kbfsmd.ID{}, err 559 } 560 j.journalID = info.ID 561 return info.ID, nil 562 } 563 564 // putMD stores the given metadata under its ID, if it's not already 565 // stored. The extra metadata is put separately, since sometimes, 566 // (e.g., when converting to a branch) we don't need to put it. 567 func (j mdJournal) putMD(rmd kbfsmd.RootMetadata) (mdID kbfsmd.ID, err error) { 568 // TODO: Make crypto and RMD wrap errors. 569 570 err = rmd.IsLastModifiedBy(j.uid, j.key) 571 if err != nil { 572 return kbfsmd.ID{}, err 573 } 574 575 id, err := kbfsmd.MakeID(j.codec, rmd) 576 if err != nil { 577 return kbfsmd.ID{}, err 578 } 579 580 _, err = ioutil.Stat(j.mdDataPath(id)) 581 switch { 582 case ioutil.IsNotExist(err): 583 // Continue on. 584 case err != nil: 585 return kbfsmd.ID{}, err 586 default: 587 // Entry exists, so nothing else to do. 588 return id, nil 589 } 590 591 err = kbfscodec.SerializeToFileIfNotExist( 592 j.codec, rmd, j.mdDataPath(id)) 593 if err != nil { 594 return kbfsmd.ID{}, err 595 } 596 597 err = j.putMDInfo(id, j.clock.Now(), rmd.Version()) 598 if err != nil { 599 return kbfsmd.ID{}, err 600 } 601 602 return id, nil 603 } 604 605 // removeMD removes the metadata (which must exist) with the given ID. 606 func (j *mdJournal) removeMD(id kbfsmd.ID) error { 607 path := j.mdPath(id) 608 err := ioutil.RemoveAll(path) 609 if err != nil { 610 return err 611 } 612 613 // Remove the parent (splayed) directory (which should exist) 614 // if it's empty. 615 err = ioutil.Remove(filepath.Dir(path)) 616 if ioutil.IsExist(err) { 617 err = nil 618 } 619 return err 620 } 621 622 // getEarliestWithExtra returns a kbfsmd.MutableRootMetadata so that it 623 // can be put in a RootMetadataSigned object. 624 func (j mdJournal) getEarliestWithExtra( 625 ctx context.Context, verifyBranchID bool) ( 626 kbfsmd.ID, kbfsmd.MutableRootMetadata, kbfsmd.ExtraMetadata, time.Time, error) { 627 entry, exists, err := j.j.getEarliestEntry() 628 if err != nil { 629 return kbfsmd.ID{}, nil, nil, time.Time{}, err 630 } 631 if !exists { 632 return kbfsmd.ID{}, nil, nil, time.Time{}, nil 633 } 634 earliest, extra, timestamp, err := 635 j.getMDAndExtra(ctx, entry, verifyBranchID) 636 if err != nil { 637 return kbfsmd.ID{}, nil, nil, time.Time{}, err 638 } 639 return entry.ID, earliest, extra, timestamp, nil 640 } 641 642 func (j mdJournal) getLatest(ctx context.Context, verifyBranchID bool) ( 643 ImmutableBareRootMetadata, error) { 644 entry, exists, err := j.j.getLatestEntry() 645 if err != nil { 646 return ImmutableBareRootMetadata{}, err 647 } 648 if !exists { 649 return ImmutableBareRootMetadata{}, nil 650 } 651 latest, extra, timestamp, err := 652 j.getMDAndExtra(ctx, entry, verifyBranchID) 653 if err != nil { 654 return ImmutableBareRootMetadata{}, err 655 } 656 return MakeImmutableBareRootMetadata( 657 latest, extra, entry.ID, timestamp), nil 658 } 659 660 func (j mdJournal) checkGetParams(ctx context.Context) ( 661 ImmutableBareRootMetadata, error) { 662 head, err := j.getLatest(ctx, true) 663 if err != nil { 664 return ImmutableBareRootMetadata{}, err 665 } 666 667 if head == (ImmutableBareRootMetadata{}) { 668 return ImmutableBareRootMetadata{}, nil 669 } 670 671 ok, err := isReader( 672 ctx, j.teamMemChecker, j.uid, head.RootMetadata, head.extra) 673 if err != nil { 674 return ImmutableBareRootMetadata{}, err 675 } 676 if !ok { 677 // TODO: Use a non-server error. 678 return ImmutableBareRootMetadata{}, kbfsmd.ServerErrorUnauthorized{} 679 } 680 681 return head, nil 682 } 683 684 func (j *mdJournal) convertToBranch( 685 ctx context.Context, bid kbfsmd.BranchID, signer kbfscrypto.Signer, 686 codec kbfscodec.Codec, tlfID tlf.ID, mdcache MDCache) (err error) { 687 if j.branchID != kbfsmd.NullBranchID { 688 return errors.Errorf( 689 "convertToBranch called with j.branchID=%s", j.branchID) 690 } 691 if bid == kbfsmd.NullBranchID { 692 return errors.Errorf( 693 "convertToBranch called with null branchID") 694 } 695 696 earliestRevision, err := j.j.readEarliestRevision() 697 if err != nil { 698 return err 699 } 700 701 latestRevision, err := j.j.readLatestRevision() 702 if err != nil { 703 return err 704 } 705 706 j.log.CDebugf( 707 ctx, "rewriting MDs %s to %s", earliestRevision, latestRevision) 708 709 _, allEntries, err := j.j.getEntryRange( 710 earliestRevision, latestRevision) 711 if err != nil { 712 return err 713 } 714 715 j.log.CDebugf(ctx, "New branch ID=%s", bid) 716 717 journalTempDir, err := ioutil.TempDir(j.dir, "md_journal") 718 if err != nil { 719 return err 720 } 721 j.log.CDebugf(ctx, "Using temp dir %s for rewriting", journalTempDir) 722 723 mdsToRemove := make([]kbfsmd.ID, 0, len(allEntries)) 724 defer func() { 725 // If we crash here and leave behind the tempdir, it 726 // won't be cleaned up automatically when the journal 727 // is completely drained, but it'll be cleaned up when 728 // the parent journal (i.e., tlfJournal) is completely 729 // drained. As for the entries, they'll be cleaned up 730 // the next time the journal is completely drained. 731 732 j.log.CDebugf(ctx, "Removing temp dir %s and %d old MDs", 733 journalTempDir, len(mdsToRemove)) 734 removeErr := ioutil.RemoveAll(journalTempDir) 735 if removeErr != nil { 736 j.log.CWarningf(ctx, 737 "Error when removing temp dir %s: %+v", 738 journalTempDir, removeErr) 739 } 740 // Garbage-collect the unnecessary MD entries. 741 for _, id := range mdsToRemove { 742 removeErr := j.removeMD(id) 743 if removeErr != nil { 744 j.log.CWarningf(ctx, "Error when removing old MD %s: %+v", 745 id, removeErr) 746 } 747 } 748 }() 749 750 tempJournal, err := makeMdIDJournal(j.codec, journalTempDir) 751 if err != nil { 752 return err 753 } 754 755 var prevID kbfsmd.ID 756 757 isPendingLocalSquash := bid == kbfsmd.PendingLocalSquashBranchID 758 for _, entry := range allEntries { 759 brmd, _, ts, err := j.getMDAndExtra(ctx, entry, true) 760 if err != nil { 761 return err 762 } 763 764 if entry.IsLocalSquash && isPendingLocalSquash { 765 // If this is a local squash, don't convert it. We don't 766 // want to squash anything more into it. 767 j.log.CDebugf(ctx, "Preserving local squash %s", entry.ID) 768 err = tempJournal.append(brmd.RevisionNumber(), entry) 769 if err != nil { 770 return err 771 } 772 continue 773 } 774 775 brmd.SetUnmerged() 776 brmd.SetBranchID(bid) 777 778 // Re-sign the writer metadata internally, since we 779 // changed it. 780 err = brmd.SignWriterMetadataInternally(ctx, j.codec, signer) 781 if err != nil { 782 j.log.CDebugf(ctx, "Early exit %d %+v", brmd.RevisionNumber(), err) 783 return err 784 } 785 786 // Set the prev root for everything after the first MD we 787 // modify, which happens to be indicated by mdsToRemove being 788 // non-empty. 789 if len(mdsToRemove) > 0 { 790 j.log.CDebugf(ctx, "Old prev root of rev=%s is %s", 791 brmd.RevisionNumber(), brmd.GetPrevRoot()) 792 j.log.CDebugf(ctx, "Changing prev root of rev=%s to %s", 793 brmd.RevisionNumber(), prevID) 794 brmd.SetPrevRoot(prevID) 795 } 796 797 // TODO: this rewrites the file, and so the modification time 798 // no longer tracks when exactly the original operation is 799 // done, so future ImmutableBareMetadatas for this MD will 800 // have a slightly wrong localTimestamp. Instead, we might 801 // want to pass in the timestamp and do an explicit 802 // os.Chtimes() on the file after writing it. 803 newID, err := j.putMD(brmd) 804 if err != nil { 805 return err 806 } 807 mdsToRemove = append(mdsToRemove, newID) 808 809 // Preserve unknown fields from the old journal. 810 newEntry := entry 811 newEntry.ID = newID 812 newEntry.IsLocalSquash = false 813 err = tempJournal.append(brmd.RevisionNumber(), newEntry) 814 if err != nil { 815 return err 816 } 817 818 prevID = newID 819 820 // If possible, replace the old RMD in the cache. If it's not 821 // already in the cache, don't bother adding it, as that will 822 // just evict something incorrectly. If it's been replaced by 823 // the REAL commit from the master branch due to a race, don't 824 // clobber that real commit. TODO: Don't replace the MD until 825 // we know for sure that the branch conversion succeeds 826 // (however, the Replace doesn't affect correctness since the 827 // original commit will be read from disk instead of the cache 828 // in the event of a conversion failure). 829 oldIrmd, err := mdcache.Get( 830 tlfID, brmd.RevisionNumber(), kbfsmd.NullBranchID) 831 if err == nil && entry.ID == oldIrmd.mdID { 832 newRmd, err := oldIrmd.deepCopy(codec) 833 if err != nil { 834 return err 835 } 836 newRmd.bareMd = brmd 837 // Everything else is the same. 838 err = mdcache.Replace( 839 MakeImmutableRootMetadata(newRmd, 840 oldIrmd.LastModifyingWriterVerifyingKey(), 841 newID, ts, false), 842 kbfsmd.NullBranchID) 843 if err != nil { 844 return err 845 } 846 } else { 847 j.log.CDebugf(ctx, 848 "Not cache-replacing rev=%d: old ID=%s, entry.ID=%s, err=%+v", 849 brmd.RevisionNumber(), oldIrmd.mdID, entry.ID, err) 850 } 851 852 j.log.CDebugf(ctx, "Changing ID for rev=%s from %s to %s", 853 brmd.RevisionNumber(), entry.ID, newID) 854 } 855 856 // TODO: Do the below atomically on the filesystem 857 // level. Specifically, make "md_journal" always be a symlink, 858 // and then perform the swap by atomically changing the 859 // symlink to point to the new journal directory. 860 861 oldJournalTempDir := journalTempDir + ".old" 862 dir, err := j.j.move(oldJournalTempDir) 863 if err != nil { 864 return err 865 } 866 867 j.log.CDebugf(ctx, "Moved old journal from %s to %s", 868 dir, oldJournalTempDir) 869 870 newJournalOldDir, err := tempJournal.move(dir) 871 if err != nil { 872 return err 873 } 874 875 j.log.CDebugf(ctx, "Moved new journal from %s to %s", 876 newJournalOldDir, dir) 877 878 // Make the defer block above remove oldJournalTempDir. 879 journalTempDir = oldJournalTempDir 880 881 mdsToRemove = make([]kbfsmd.ID, 0, len(allEntries)) 882 for _, entry := range allEntries { 883 if entry.IsLocalSquash && isPendingLocalSquash { 884 continue 885 } 886 mdsToRemove = append(mdsToRemove, entry.ID) 887 } 888 889 j.j = tempJournal 890 j.branchID = bid 891 j.journalID = kbfsmd.ID{} 892 893 return nil 894 } 895 896 // getNextEntryToFlush returns the info for the next journal entry to 897 // flush, if it exists, and its revision is less than end. If there is 898 // no next journal entry to flush, the returned MdID will be zero, and 899 // the returned *RootMetadataSigned will be nil. 900 func (j mdJournal) getNextEntryToFlush( 901 ctx context.Context, end kbfsmd.Revision, signer kbfscrypto.Signer) ( 902 kbfsmd.ID, *RootMetadataSigned, kbfsmd.ExtraMetadata, error) { 903 mdID, rmd, extra, timestamp, err := j.getEarliestWithExtra(ctx, true) 904 if err != nil { 905 return kbfsmd.ID{}, nil, nil, err 906 } 907 if rmd == nil || rmd.RevisionNumber() >= end { 908 return kbfsmd.ID{}, nil, nil, nil 909 } 910 911 rmds, err := SignBareRootMetadata( 912 ctx, j.codec, signer, signer, rmd, timestamp) 913 if err != nil { 914 return kbfsmd.ID{}, nil, nil, err 915 } 916 917 return mdID, rmds, extra, nil 918 } 919 920 func (j *mdJournal) removeFlushedEntry( 921 ctx context.Context, mdID kbfsmd.ID, rmds *RootMetadataSigned) ( 922 clearedMDJournal bool, err error) { 923 rmdID, rmd, _, _, err := j.getEarliestWithExtra(ctx, true) 924 if err != nil { 925 return false, err 926 } 927 if rmd == nil { 928 return false, errors.New("mdJournal unexpectedly empty") 929 } 930 931 if mdID != rmdID { 932 return false, errors.Errorf("Expected mdID %s, got %s", mdID, rmdID) 933 } 934 935 eq, err := kbfscodec.Equal(j.codec, rmd, rmds.MD) 936 if err != nil { 937 return false, err 938 } 939 if !eq { 940 return false, errors.New( 941 "Given RootMetadataSigned doesn't match earliest") 942 } 943 944 empty, err := j.j.removeEarliest() 945 if err != nil { 946 return false, err 947 } 948 949 // Since the journal is now empty, set lastMdID and nuke all 950 // MD-related directories. 951 if empty { 952 j.log.CDebugf(ctx, 953 "MD journal is now empty; saving last MdID=%s", mdID) 954 j.lastMdID = mdID 955 956 // The disk journal has already been cleared, so we 957 // can nuke the directories without having to worry 958 // about putting the journal in a weird state if we 959 // crash in the middle. The various directories will 960 // be recreated as needed. 961 for _, dir := range j.mdJournalDirs() { 962 j.log.CDebugf(ctx, "Removing all files in %s", dir) 963 err := ioutil.RemoveAll(dir) 964 if err != nil { 965 return false, err 966 } 967 } 968 969 return true, nil 970 } 971 972 // Garbage-collect the old entry. If we crash here and 973 // leave behind an entry, it'll be cleaned up the next 974 // time the journal is completely drained. 975 err = j.removeMD(mdID) 976 if err != nil { 977 return false, err 978 } 979 980 return false, nil 981 } 982 983 func getMdID(ctx context.Context, mdserver MDServer, codec kbfscodec.Codec, 984 tlfID tlf.ID, bid kbfsmd.BranchID, mStatus kbfsmd.MergeStatus, 985 revision kbfsmd.Revision, lockBeforeGet *keybase1.LockID) (kbfsmd.ID, error) { 986 rmdses, err := mdserver.GetRange( 987 ctx, tlfID, bid, mStatus, revision, revision, lockBeforeGet) 988 switch { 989 case err != nil: 990 return kbfsmd.ID{}, err 991 case len(rmdses) == 0: 992 return kbfsmd.ID{}, nil 993 case len(rmdses) > 1: 994 return kbfsmd.ID{}, errors.Errorf( 995 "Got more than one object when trying to get rev=%d for branch %s of TLF %s", 996 revision, bid, tlfID) 997 } 998 999 return kbfsmd.MakeID(codec, rmdses[0].MD) 1000 } 1001 1002 // clearHelper removes all the journal entries starting from 1003 // earliestBranchRevision and deletes the corresponding MD 1004 // updates. All MDs from earliestBranchRevision onwards must have 1005 // branch equal to the given one, which must not be kbfsmd.NullBranchID. This 1006 // means that, if bid != kbfsmd.PendingLocalSquashBranchID, 1007 // earliestBranchRevision must equal the earliest revision, and if bid 1008 // == kbfsmd.PendingLocalSquashBranchID, earliestBranchRevision must equal 1009 // one past the last local squash revision. If the branch is a pending 1010 // local squash, it preserves the MD updates corresponding to the 1011 // prefix of existing local squashes, so they can be re-used in the 1012 // newly-resolved journal. 1013 func (j *mdJournal) clearHelper(ctx context.Context, bid kbfsmd.BranchID, 1014 earliestBranchRevision kbfsmd.Revision) (err error) { 1015 j.log.CDebugf(ctx, "Clearing journal for branch %s", bid) 1016 defer func() { 1017 if err != nil { 1018 j.deferLog.CDebugf(ctx, 1019 "Clearing journal for branch %s failed with %+v", 1020 bid, err) 1021 } 1022 }() 1023 1024 if bid == kbfsmd.NullBranchID { 1025 return errors.New("Cannot clear master branch") 1026 } 1027 1028 if j.branchID != bid { 1029 // Nothing to do. 1030 j.log.CDebugf(ctx, "Ignoring clear for branch %s while on branch %s", 1031 bid, j.branchID) 1032 return nil 1033 } 1034 1035 head, err := j.getHead(ctx, bid) 1036 if err != nil { 1037 return err 1038 } 1039 1040 if head == (ImmutableBareRootMetadata{}) { 1041 // The journal has been flushed but not cleared yet. 1042 j.branchID = kbfsmd.NullBranchID 1043 j.journalID = kbfsmd.ID{} 1044 return nil 1045 } 1046 1047 if head.BID() != j.branchID { 1048 return errors.Errorf("Head branch ID %s doesn't match journal "+ 1049 "branch ID %s while clearing", head.BID(), j.branchID) 1050 } 1051 1052 latestRevision, err := j.j.readLatestRevision() 1053 if err != nil { 1054 return err 1055 } 1056 1057 _, allEntries, err := j.j.getEntryRange( 1058 earliestBranchRevision, latestRevision) 1059 if err != nil { 1060 return err 1061 } 1062 1063 err = j.j.clearFrom(earliestBranchRevision) 1064 if err != nil { 1065 return err 1066 } 1067 1068 j.branchID = kbfsmd.NullBranchID 1069 j.journalID = kbfsmd.ID{} 1070 1071 // No need to set lastMdID in this case. 1072 1073 // Garbage-collect the old branch entries. TODO: we'll eventually 1074 // need a sweeper to clean up entries left behind if we crash 1075 // here. 1076 for _, entry := range allEntries { 1077 err := j.removeMD(entry.ID) 1078 if err != nil { 1079 return err 1080 } 1081 } 1082 return nil 1083 } 1084 1085 // All functions below are public functions. 1086 1087 func (j mdJournal) readEarliestRevision() (kbfsmd.Revision, error) { 1088 return j.j.readEarliestRevision() 1089 } 1090 1091 func (j mdJournal) readLatestRevision() (kbfsmd.Revision, error) { 1092 return j.j.readLatestRevision() 1093 } 1094 1095 func (j mdJournal) length() uint64 { 1096 return j.j.length() 1097 } 1098 1099 func (j mdJournal) atLeastNNonLocalSquashes( 1100 numNonLocalSquashes uint64) (bool, error) { 1101 size := j.length() 1102 if size < numNonLocalSquashes { 1103 return false, nil 1104 } 1105 1106 latestRev, err := j.readLatestRevision() 1107 if err != nil { 1108 return false, err 1109 } 1110 1111 // Since the IsLocalSquash entries are guaranteed to be a prefix 1112 // of the journal, we can just look up an entry that's back 1113 // `numNonLocalSquashes` entries ago, and see if it's a local 1114 // squash or not. 1115 entry, err := j.j.readJournalEntry( 1116 latestRev - kbfsmd.Revision(numNonLocalSquashes) + 1) 1117 if err != nil { 1118 return false, err 1119 } 1120 1121 return !entry.IsLocalSquash, nil 1122 } 1123 1124 func (j mdJournal) end() (kbfsmd.Revision, error) { 1125 return j.j.end() 1126 } 1127 1128 func (j mdJournal) getBranchID() kbfsmd.BranchID { 1129 return j.branchID 1130 } 1131 1132 func (j mdJournal) getHead(ctx context.Context, bid kbfsmd.BranchID) ( 1133 ImmutableBareRootMetadata, error) { 1134 head, err := j.checkGetParams(ctx) 1135 if err != nil { 1136 return ImmutableBareRootMetadata{}, err 1137 } 1138 if head == (ImmutableBareRootMetadata{}) { 1139 return ImmutableBareRootMetadata{}, nil 1140 } 1141 1142 getLocalSquashHead := bid == kbfsmd.NullBranchID && 1143 j.branchID == kbfsmd.PendingLocalSquashBranchID 1144 if !getLocalSquashHead { 1145 if head.BID() != bid { 1146 return ImmutableBareRootMetadata{}, nil 1147 } 1148 return head, nil 1149 } 1150 1151 // Look backwards in the journal for the first entry with 1152 // IsLocalSquash set to true. 1153 earliestRev, err := j.readEarliestRevision() 1154 if err != nil { 1155 return ImmutableBareRootMetadata{}, err 1156 } 1157 1158 latestRev, err := j.readLatestRevision() 1159 if err != nil { 1160 return ImmutableBareRootMetadata{}, err 1161 } 1162 1163 for rev := latestRev; rev >= earliestRev; rev-- { 1164 entry, err := j.j.readJournalEntry(rev) 1165 if err != nil { 1166 return ImmutableBareRootMetadata{}, err 1167 } 1168 if entry.IsLocalSquash { 1169 latest, extra, timestamp, err := 1170 j.getMDAndExtra(ctx, entry, false) 1171 if err != nil { 1172 return ImmutableBareRootMetadata{}, err 1173 } 1174 return MakeImmutableBareRootMetadata( 1175 latest, extra, entry.ID, timestamp), nil 1176 } 1177 } 1178 return ImmutableBareRootMetadata{}, nil 1179 } 1180 1181 func (j mdJournal) getRange( 1182 ctx context.Context, bid kbfsmd.BranchID, start, stop kbfsmd.Revision) ( 1183 []ImmutableBareRootMetadata, error) { 1184 head, err := j.checkGetParams(ctx) 1185 if err != nil { 1186 return nil, err 1187 } else if head == (ImmutableBareRootMetadata{}) { 1188 return nil, nil 1189 } 1190 1191 // If we are on a pending local squash branch, the caller can ask 1192 // for "merged" entries that make up a prefix of the journal. 1193 getLocalSquashPrefix := bid == kbfsmd.NullBranchID && 1194 j.branchID == kbfsmd.PendingLocalSquashBranchID 1195 if head.BID() != bid && !getLocalSquashPrefix { 1196 return nil, nil 1197 } 1198 1199 realStart, entries, err := j.j.getEntryRange(start, stop) 1200 if err != nil { 1201 return nil, err 1202 } 1203 var ibrmds []ImmutableBareRootMetadata 1204 for i, entry := range entries { 1205 if getLocalSquashPrefix && !entry.IsLocalSquash { 1206 // We only need the prefix up to the first non-local-squash. 1207 break 1208 } else if entry.IsLocalSquash && bid == kbfsmd.PendingLocalSquashBranchID { 1209 // Ignore the local squash prefix of this journal. 1210 continue 1211 } 1212 1213 expectedRevision := realStart + kbfsmd.Revision(i) 1214 brmd, extra, ts, err := j.getMDAndExtra(ctx, entry, true) 1215 if err != nil { 1216 return nil, err 1217 } 1218 1219 if expectedRevision != brmd.RevisionNumber() { 1220 panic(errors.Errorf("expected revision %v, got %v", 1221 expectedRevision, brmd.RevisionNumber())) 1222 } 1223 ibrmd := MakeImmutableBareRootMetadata( 1224 brmd, extra, entry.ID, ts) 1225 ibrmds = append(ibrmds, ibrmd) 1226 } 1227 1228 return ibrmds, nil 1229 } 1230 1231 // MDJournalConflictError is an error that is returned when a put 1232 // detects a rewritten journal. 1233 type MDJournalConflictError struct{} 1234 1235 func (e MDJournalConflictError) Error() string { 1236 return "MD journal conflict error" 1237 } 1238 1239 // put verifies and stores the given RootMetadata in the journal, 1240 // modifying it as needed. In particular, there are four cases: 1241 // 1242 // Merged 1243 // ------ 1244 // rmd is merged. If the journal is empty, then rmd becomes the 1245 // initial entry. Otherwise, if the journal has been converted to a 1246 // branch, then an MDJournalConflictError error is returned, and the 1247 // caller is expected to set the unmerged bit and retry (see case 1248 // Unmerged-1). Otherwise, either rmd must be the successor to the 1249 // journal's head, in which case it is appended, or it must have the 1250 // same revision number as the journal's head, in which case it 1251 // replaces the journal's head. (This is necessary since if a journal 1252 // put is cancelled and an error is returned, it still happens, and so 1253 // we want the retried put (if any) to not conflict with it.) 1254 // 1255 // Unmerged-1 1256 // ---------- 1257 // rmd is unmerged and has a null branch ID. This happens when case 1258 // Merged returns with MDJournalConflictError. In this case, the rmd's 1259 // branch ID is set to the journal's branch ID and its prevRoot is set 1260 // to the last known journal root. It doesn't matter if the journal is 1261 // completely drained, since the branch ID and last known root is 1262 // remembered in memory. However, since this cache isn't persisted to 1263 // disk, we need case Unmerged-3. Similarly to case Merged, this case 1264 // then also does append-or-replace. 1265 // 1266 // Unmerged-2 1267 // ---------- 1268 // rmd is unmerged and has a non-null branch ID, and the journal was 1269 // non-empty at some time during this process's lifetime. Similarly to 1270 // case Merged, if the journal is empty, then rmd becomes the initial 1271 // entry, and otherwise, this case does append-or-replace. 1272 // 1273 // Unmerged-3 1274 // ---------- 1275 // rmd is unmerged and has a non-null branch ID, and the journal has 1276 // always been empty during this process's lifetime. The branch ID is 1277 // assumed to be correct, i.e. retrieved from the remote MDServer, and 1278 // rmd becomes the initial entry. 1279 func (j *mdJournal) put( 1280 ctx context.Context, signer kbfscrypto.Signer, 1281 ekg encryptionKeyGetter, bsplit data.BlockSplitter, rmd *RootMetadata, 1282 isLocalSquash bool) ( 1283 mdID, journalID kbfsmd.ID, err error) { 1284 j.log.CDebugf(ctx, "Putting MD for TLF=%s with rev=%s bid=%s", 1285 rmd.TlfID(), rmd.Revision(), rmd.BID()) 1286 defer func() { 1287 if err != nil { 1288 j.deferLog.CDebugf(ctx, 1289 "Put MD for TLF=%s with rev=%s bid=%s failed with %+v", 1290 rmd.TlfID(), rmd.Revision(), rmd.BID(), err) 1291 } 1292 }() 1293 1294 head, err := j.getLatest(ctx, true) 1295 if err != nil { 1296 return kbfsmd.ID{}, kbfsmd.ID{}, err 1297 } 1298 1299 mStatus := rmd.MergedStatus() 1300 1301 // Make modifications for the Unmerged cases. 1302 if mStatus == kbfsmd.Unmerged { 1303 var lastMdID kbfsmd.ID 1304 if head == (ImmutableBareRootMetadata{}) { 1305 lastMdID = j.lastMdID 1306 } else { 1307 lastMdID = head.mdID 1308 } 1309 1310 if rmd.BID() == kbfsmd.NullBranchID && j.branchID == kbfsmd.NullBranchID { 1311 return kbfsmd.ID{}, kbfsmd.ID{}, errors.New( 1312 "Unmerged put with rmd.BID() == j.branchID == kbfsmd.NullBranchID") 1313 } 1314 1315 switch { 1316 case head == (ImmutableBareRootMetadata{}) && 1317 j.branchID == kbfsmd.NullBranchID: 1318 // Case Unmerged-3. 1319 j.branchID = rmd.BID() 1320 // Revert branch ID if we encounter an error. 1321 defer func() { 1322 if err != nil { 1323 j.branchID = kbfsmd.NullBranchID 1324 } 1325 }() 1326 case rmd.BID() == kbfsmd.NullBranchID: 1327 // Case Unmerged-1. 1328 j.log.CDebugf( 1329 ctx, "Changing branch ID to %s and prev root to %s for MD for TLF=%s with rev=%s", 1330 j.branchID, lastMdID, rmd.TlfID(), rmd.Revision()) 1331 rmd.SetBranchID(j.branchID) 1332 rmd.SetPrevRoot(lastMdID) 1333 default: // nolint 1334 // Using de Morgan's laws, this branch is 1335 // taken when both rmd.BID() is non-null, and 1336 // either head is non-empty or j.branchID is 1337 // non-empty. So this is most of case 1338 // Unmerged-2, and there's nothing to do. 1339 // 1340 // The remaining part of case Unmerged-2, 1341 // where rmd.BID() is non-null, head is empty, 1342 // and j.branchID is empty, is an error case, 1343 // handled below. 1344 } 1345 } 1346 1347 // The below is code common to all the cases. 1348 1349 if (mStatus == kbfsmd.Merged) != (rmd.BID() == kbfsmd.NullBranchID) { 1350 return kbfsmd.ID{}, kbfsmd.ID{}, errors.Errorf( 1351 "mStatus=%s doesn't match bid=%s", mStatus, rmd.BID()) 1352 } 1353 1354 // If we're trying to push a merged MD onto a branch, return a 1355 // conflict error so the caller can retry with an unmerged MD. 1356 if mStatus == kbfsmd.Merged && j.branchID != kbfsmd.NullBranchID { 1357 return kbfsmd.ID{}, kbfsmd.ID{}, MDJournalConflictError{} 1358 } 1359 1360 if rmd.BID() != j.branchID { 1361 return kbfsmd.ID{}, kbfsmd.ID{}, errors.Errorf( 1362 "Branch ID mismatch: expected %s, got %s", 1363 j.branchID, rmd.BID()) 1364 } 1365 1366 if isLocalSquash && rmd.BID() != kbfsmd.NullBranchID { 1367 return kbfsmd.ID{}, kbfsmd.ID{}, 1368 errors.Errorf("A local squash must have a null branch ID,"+ 1369 " but this one has bid=%s", rmd.BID()) 1370 } 1371 1372 // Check permissions and consistency with head, if it exists. 1373 if head != (ImmutableBareRootMetadata{}) { 1374 ok, err := isWriterOrValidRekey( 1375 ctx, j.teamMemChecker, j.codec, j.uid, j.key, head.RootMetadata, 1376 rmd.bareMd, head.extra, rmd.extra) 1377 if err != nil { 1378 return kbfsmd.ID{}, kbfsmd.ID{}, err 1379 } 1380 if !ok { 1381 // TODO: Use a non-server error. 1382 return kbfsmd.ID{}, kbfsmd.ID{}, kbfsmd.ServerErrorUnauthorized{} 1383 } 1384 1385 // Consistency checks 1386 if rmd.Revision() != head.RevisionNumber() { 1387 err = head.CheckValidSuccessorForServer( 1388 head.mdID, rmd.bareMd) 1389 if err != nil { 1390 return kbfsmd.ID{}, kbfsmd.ID{}, err 1391 } 1392 } 1393 1394 // Local squashes should only be preceded by another local 1395 // squash in the journal. 1396 if isLocalSquash { 1397 entry, exists, err := j.j.getLatestEntry() 1398 if err != nil { 1399 return kbfsmd.ID{}, kbfsmd.ID{}, err 1400 } 1401 if exists && !entry.IsLocalSquash { 1402 return kbfsmd.ID{}, kbfsmd.ID{}, 1403 errors.Errorf("Local squash is not preceded "+ 1404 "by a local squash (head=%s)", entry.ID) 1405 } 1406 } 1407 } 1408 1409 // Ensure that the block changes are properly unembedded. 1410 if rmd.data.Changes.Info.BlockPointer == data.ZeroPtr && 1411 !bsplit.ShouldEmbedData(rmd.data.Changes.SizeEstimate()) { 1412 return kbfsmd.ID{}, kbfsmd.ID{}, 1413 errors.New("MD has embedded block changes, but shouldn't") 1414 } 1415 1416 err = encryptMDPrivateData( 1417 ctx, j.codec, j.crypto, signer, ekg, j.uid, rmd) 1418 if err != nil { 1419 return kbfsmd.ID{}, kbfsmd.ID{}, err 1420 } 1421 1422 err = rmd.bareMd.IsValidAndSigned( 1423 ctx, j.codec, j.teamMemChecker, rmd.extra, j.key, 1424 j.osg.OfflineAvailabilityForID(j.tlfID)) 1425 if err != nil { 1426 return kbfsmd.ID{}, kbfsmd.ID{}, err 1427 } 1428 1429 id, err := j.putMD(rmd.bareMd) 1430 if err != nil { 1431 return kbfsmd.ID{}, kbfsmd.ID{}, err 1432 } 1433 1434 wkbNew, rkbNew, err := j.putExtraMetadata(rmd.bareMd, rmd.extra) 1435 if err != nil { 1436 return kbfsmd.ID{}, kbfsmd.ID{}, err 1437 } 1438 1439 newEntry := mdIDJournalEntry{ 1440 ID: id, 1441 IsLocalSquash: isLocalSquash, 1442 WKBNew: wkbNew, 1443 RKBNew: rkbNew, 1444 } 1445 if head != (ImmutableBareRootMetadata{}) && 1446 rmd.Revision() == head.RevisionNumber() { 1447 1448 j.log.CDebugf( 1449 ctx, "Replacing head MD for TLF=%s with rev=%s bid=%s", 1450 rmd.TlfID(), rmd.Revision(), rmd.BID()) 1451 // Don't try and preserve unknown fields from the old 1452 // head here -- the new head is in general a different 1453 // MD, so the unknown fields from the old head won't 1454 // make sense. 1455 err = j.j.replaceHead(newEntry) 1456 if err != nil { 1457 return kbfsmd.ID{}, kbfsmd.ID{}, err 1458 } 1459 } else { 1460 err = j.j.append(rmd.Revision(), newEntry) 1461 if err != nil { 1462 return kbfsmd.ID{}, kbfsmd.ID{}, err 1463 } 1464 } 1465 1466 // Since the journal is now non-empty, clear lastMdID. 1467 j.lastMdID = kbfsmd.ID{} 1468 1469 journalID, err = j.getOrCreateJournalID() 1470 if err != nil { 1471 return kbfsmd.ID{}, kbfsmd.ID{}, err 1472 } 1473 1474 return id, journalID, nil 1475 } 1476 1477 // clear removes all the journal entries, and deletes the 1478 // corresponding MD updates. If the branch is a pending local squash, 1479 // it preserves the MD updates corresponding to the prefix of existing 1480 // local squashes, so they can be re-used in the newly-resolved 1481 // journal. 1482 func (j *mdJournal) clear(ctx context.Context, bid kbfsmd.BranchID) error { 1483 earliestBranchRevision, err := j.j.readEarliestRevision() 1484 if err != nil { 1485 return err 1486 } 1487 1488 if earliestBranchRevision != kbfsmd.RevisionUninitialized && 1489 bid == kbfsmd.PendingLocalSquashBranchID { 1490 latestRevision, err := j.j.readLatestRevision() 1491 if err != nil { 1492 return err 1493 } 1494 1495 for ; earliestBranchRevision <= latestRevision; earliestBranchRevision++ { 1496 entry, err := j.j.readJournalEntry(earliestBranchRevision) 1497 if err != nil { 1498 return err 1499 } 1500 if !entry.IsLocalSquash { 1501 break 1502 } 1503 } 1504 } 1505 1506 return j.clearHelper(ctx, bid, earliestBranchRevision) 1507 } 1508 1509 func (j *mdJournal) resolveAndClear( 1510 ctx context.Context, signer kbfscrypto.Signer, ekg encryptionKeyGetter, 1511 bsplit data.BlockSplitter, mdcache MDCache, bid kbfsmd.BranchID, 1512 rmd *RootMetadata) (mdID, journalID kbfsmd.ID, err error) { 1513 j.log.CDebugf(ctx, "Resolve and clear, branch %s, resolve rev %d", 1514 bid, rmd.Revision()) 1515 defer func() { 1516 if err != nil { 1517 j.deferLog.CDebugf(ctx, 1518 "Resolving journal for branch %s failed with %+v", 1519 bid, err) 1520 } 1521 }() 1522 1523 // The resolution must not have a branch ID. 1524 if rmd.BID() != kbfsmd.NullBranchID { 1525 return kbfsmd.ID{}, kbfsmd.ID{}, 1526 errors.Errorf("Resolution MD has branch ID: %s", rmd.BID()) 1527 } 1528 1529 // The branch ID must match our current state. 1530 if bid == kbfsmd.NullBranchID { 1531 return kbfsmd.ID{}, kbfsmd.ID{}, 1532 errors.New("Cannot resolve master branch") 1533 } 1534 if j.branchID != bid { 1535 return kbfsmd.ID{}, kbfsmd.ID{}, 1536 errors.Errorf("Resolve and clear for branch %s "+ 1537 "while on branch %s", bid, j.branchID) 1538 } 1539 1540 earliestBranchRevision, err := j.j.readEarliestRevision() 1541 if err != nil { 1542 return kbfsmd.ID{}, kbfsmd.ID{}, err 1543 } 1544 1545 latestRevision, err := j.j.readLatestRevision() 1546 if err != nil { 1547 return kbfsmd.ID{}, kbfsmd.ID{}, err 1548 } 1549 1550 // First make a new journal to hold the block. 1551 1552 // Give this new journal a new ID journal. 1553 idJournalTempDir, err := ioutil.TempDir(j.dir, "md_journal") 1554 if err != nil { 1555 return kbfsmd.ID{}, kbfsmd.ID{}, err 1556 } 1557 1558 // TODO: If we crash without removing the temp dir, it should 1559 // be cleaned up whenever the entire journal goes empty. 1560 1561 j.log.CDebugf(ctx, "Using temp dir %s for new IDs", idJournalTempDir) 1562 otherIDJournal, err := makeMdIDJournal(j.codec, idJournalTempDir) 1563 if err != nil { 1564 return kbfsmd.ID{}, kbfsmd.ID{}, err 1565 } 1566 defer func() { 1567 j.log.CDebugf(ctx, "Removing temp dir %s", idJournalTempDir) 1568 removeErr := ioutil.RemoveAll(idJournalTempDir) 1569 if removeErr != nil { 1570 j.log.CWarningf(ctx, 1571 "Error when removing temp dir %s: %+v", 1572 idJournalTempDir, removeErr) 1573 } 1574 }() 1575 1576 otherJournal, err := makeMDJournalWithIDJournal( 1577 ctx, j.uid, j.key, j.codec, j.crypto, j.clock, j.teamMemChecker, j.osg, 1578 j.tlfID, j.mdVer, j.dir, otherIDJournal, j.log, j.overrideTlfID) 1579 if err != nil { 1580 return kbfsmd.ID{}, kbfsmd.ID{}, err 1581 } 1582 1583 // Put the local squashes back into the new journal, since they 1584 // weren't part of the resolve. 1585 if bid == kbfsmd.PendingLocalSquashBranchID { 1586 for ; earliestBranchRevision <= latestRevision; earliestBranchRevision++ { 1587 entry, err := j.j.readJournalEntry(earliestBranchRevision) 1588 if err != nil { 1589 return kbfsmd.ID{}, kbfsmd.ID{}, err 1590 } 1591 if !entry.IsLocalSquash { 1592 break 1593 } 1594 j.log.CDebugf(ctx, "Preserving entry %s", entry.ID) 1595 err = otherIDJournal.append(earliestBranchRevision, entry) 1596 if err != nil { 1597 return kbfsmd.ID{}, kbfsmd.ID{}, err 1598 } 1599 } 1600 } 1601 1602 mdID, journalID, err = otherJournal.put( 1603 ctx, signer, ekg, bsplit, rmd, true) 1604 if err != nil { 1605 return kbfsmd.ID{}, kbfsmd.ID{}, err 1606 } 1607 1608 // Transform this journal into the new one. 1609 1610 // TODO: Do the below atomically on the filesystem 1611 // level. Specifically, make "md_journal" always be a symlink, 1612 // and then perform the swap by atomically changing the 1613 // symlink to point to the new journal directory. 1614 1615 oldIDJournalTempDir := idJournalTempDir + ".old" 1616 dir, err := j.j.move(oldIDJournalTempDir) 1617 if err != nil { 1618 return kbfsmd.ID{}, kbfsmd.ID{}, err 1619 } 1620 1621 j.log.CDebugf(ctx, "Moved old journal from %s to %s", 1622 dir, oldIDJournalTempDir) 1623 1624 otherIDJournalOldDir, err := otherJournal.j.move(dir) 1625 if err != nil { 1626 return kbfsmd.ID{}, kbfsmd.ID{}, err 1627 } 1628 1629 // Set new journal to one with the new revision. 1630 j.log.CDebugf(ctx, "Moved new journal from %s to %s", 1631 otherIDJournalOldDir, dir) 1632 1633 // Transform the other journal into the old journal and clear 1634 // it out. 1635 *j, *otherJournal = *otherJournal, *j 1636 err = otherJournal.clearHelper(ctx, bid, earliestBranchRevision) 1637 if err != nil { 1638 return kbfsmd.ID{}, kbfsmd.ID{}, err 1639 } 1640 1641 // Make the defer above remove the old temp dir. 1642 idJournalTempDir = oldIDJournalTempDir 1643 1644 // Delete all of the branch MDs from the md cache. 1645 for rev := earliestBranchRevision; rev <= latestRevision; rev++ { 1646 mdcache.Delete(j.tlfID, rev, bid) 1647 } 1648 1649 return mdID, journalID, nil 1650 } 1651 1652 // markLatestAsLocalSquash marks the head revision as a local squash, 1653 // without the need to go through resolveAndClear. It's assumed that 1654 // the caller already guaranteed that there is no more than 1 1655 // non-local-squash at the end of the journal. 1656 func (j *mdJournal) markLatestAsLocalSquash(ctx context.Context) error { 1657 if j.branchID != kbfsmd.NullBranchID { 1658 return errors.Errorf("Can't mark latest as local squash when on a "+ 1659 "branch (bid=%s)", j.branchID) 1660 } 1661 1662 entry, exists, err := j.j.getLatestEntry() 1663 if err != nil { 1664 return err 1665 } 1666 if !exists || entry.IsLocalSquash { 1667 return nil 1668 } 1669 1670 entry.IsLocalSquash = true 1671 return j.j.replaceHead(entry) 1672 }