github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/journal_md_ops.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 "fmt" 9 "time" 10 11 "github.com/keybase/client/go/kbfs/data" 12 "github.com/keybase/client/go/kbfs/kbfsblock" 13 "github.com/keybase/client/go/kbfs/kbfscrypto" 14 "github.com/keybase/client/go/kbfs/kbfsmd" 15 "github.com/keybase/client/go/kbfs/tlf" 16 "github.com/keybase/client/go/kbfs/tlfhandle" 17 "github.com/keybase/client/go/protocol/keybase1" 18 "github.com/pkg/errors" 19 "golang.org/x/net/context" 20 ) 21 22 // journalMDOps is an implementation of MDOps that delegates to a 23 // TLF's mdJournal, if one exists. Specifically, it intercepts put 24 // calls to write to the journal instead of the MDServer, where 25 // something else is presumably flushing the journal to the MDServer. 26 // 27 // It then intercepts get calls to provide a combined view of the MDs 28 // from the journal and the server when the journal is 29 // non-empty. Specifically, if rev is the earliest revision in the 30 // journal, and BID is the branch ID of the journal (which can only 31 // have one), then any requests for revisions >= rev on BID will be 32 // served from the journal instead of the server. If BID is empty, 33 // i.e. the journal is holding merged revisions, then this means that 34 // all merged revisions on the server from rev are hidden. 35 // 36 // TODO: This makes server updates meaningless for revisions >= 37 // rev. Fix this. 38 type journalMDOps struct { 39 MDOps 40 jManager *JournalManager 41 } 42 43 var _ MDOps = journalMDOps{} 44 45 // convertImmutableBareRMDToIRMD decrypts the bare MD into a 46 // full-fledged RMD. The MD is assumed to have been read from the 47 // journal. 48 func (j journalMDOps) convertImmutableBareRMDToIRMD(ctx context.Context, 49 ibrmd ImmutableBareRootMetadata, handle *tlfhandle.Handle, 50 uid keybase1.UID, key kbfscrypto.VerifyingKey) ( 51 ImmutableRootMetadata, error) { 52 // TODO: Avoid having to do this type assertion. 53 brmd, ok := ibrmd.RootMetadata.(kbfsmd.MutableRootMetadata) 54 if !ok { 55 return ImmutableRootMetadata{}, kbfsmd.MutableRootMetadataNoImplError{} 56 } 57 58 rmd := makeRootMetadata(brmd, ibrmd.extra, handle) 59 60 config := j.jManager.config 61 pmd, err := decryptMDPrivateData(ctx, config.Codec(), config.Crypto(), 62 config.BlockCache(), config.BlockOps(), config.KeyManager(), 63 config.KBPKI(), config, config.Mode(), uid, 64 rmd.GetSerializedPrivateMetadata(), rmd, rmd, j.jManager.log) 65 if err != nil { 66 return ImmutableRootMetadata{}, err 67 } 68 69 rmd.data = pmd 70 irmd := MakeImmutableRootMetadata( 71 rmd, key, ibrmd.mdID, ibrmd.localTimestamp, false) 72 return irmd, nil 73 } 74 75 // getHeadFromJournal returns the head RootMetadata for the TLF with 76 // the given ID stored in the journal, assuming it exists and matches 77 // the given branch ID and merge status. As a special case, if bid is 78 // kbfsmd.NullBranchID and mStatus is Unmerged, the branch ID check is 79 // skipped. 80 func (j journalMDOps) getHeadFromJournal( 81 ctx context.Context, id tlf.ID, bid kbfsmd.BranchID, mStatus kbfsmd.MergeStatus, 82 handle *tlfhandle.Handle) ( 83 ImmutableRootMetadata, error) { 84 tlfJournal, ok := j.jManager.getTLFJournal(id, handle) 85 if !ok { 86 return ImmutableRootMetadata{}, nil 87 } 88 89 if mStatus == kbfsmd.Unmerged && bid == kbfsmd.NullBranchID { 90 // We need to look up the branch ID because the caller didn't 91 // know it. 92 var err error 93 bid, err = tlfJournal.getBranchID() 94 if err != nil { 95 return ImmutableRootMetadata{}, err 96 } 97 } 98 99 head, err := tlfJournal.getMDHead(ctx, bid) 100 switch errors.Cause(err).(type) { 101 case nil: 102 break 103 case errTLFJournalDisabled: 104 return ImmutableRootMetadata{}, nil 105 default: 106 return ImmutableRootMetadata{}, err 107 } 108 109 if head == (ImmutableBareRootMetadata{}) { 110 return ImmutableRootMetadata{}, nil 111 } 112 113 if head.MergedStatus() != mStatus { 114 return ImmutableRootMetadata{}, nil 115 } 116 117 if mStatus == kbfsmd.Unmerged && bid != kbfsmd.NullBranchID && bid != head.BID() { 118 // The given branch ID doesn't match the one in the 119 // journal, which can only be an error. 120 return ImmutableRootMetadata{}, 121 fmt.Errorf("Expected branch ID %s, got %s", 122 bid, head.BID()) 123 } 124 125 headBareHandle, err := head.MakeBareTlfHandleWithExtra() 126 if err != nil { 127 return ImmutableRootMetadata{}, err 128 } 129 130 if handle == nil { 131 handle, err = tlfhandle.MakeHandleWithTlfID( 132 ctx, headBareHandle, id.Type(), j.jManager.config.KBPKI(), 133 j.jManager.config.KBPKI(), id, 134 j.jManager.config.OfflineAvailabilityForID(id)) 135 if err != nil { 136 return ImmutableRootMetadata{}, err 137 } 138 handle.SetTlfID(id) 139 } else { 140 // Check for mutual handle resolution. 141 headHandle, err := tlfhandle.MakeHandleWithTlfID( 142 ctx, headBareHandle, id.Type(), j.jManager.config.KBPKI(), 143 j.jManager.config.KBPKI(), id, 144 j.jManager.config.OfflineAvailabilityForID(id)) 145 if err != nil { 146 return ImmutableRootMetadata{}, err 147 } 148 149 if err := headHandle.MutuallyResolvesTo(ctx, j.jManager.config.Codec(), 150 j.jManager.config.KBPKI(), j.jManager.config.MDOps(), 151 j.jManager.config, *handle, head.RevisionNumber(), head.TlfID(), 152 j.jManager.log); err != nil { 153 return ImmutableRootMetadata{}, err 154 } 155 } 156 157 irmd, err := j.convertImmutableBareRMDToIRMD( 158 ctx, head, handle, tlfJournal.uid, tlfJournal.key) 159 if err != nil { 160 return ImmutableRootMetadata{}, err 161 } 162 163 return irmd, nil 164 } 165 166 func (j journalMDOps) getRangeFromJournal( 167 ctx context.Context, id tlf.ID, bid kbfsmd.BranchID, mStatus kbfsmd.MergeStatus, 168 start, stop kbfsmd.Revision) ( 169 []ImmutableRootMetadata, error) { 170 tlfJournal, ok := j.jManager.getTLFJournal(id, nil) 171 if !ok { 172 return nil, nil 173 } 174 175 ibrmds, err := tlfJournal.getMDRange(ctx, bid, start, stop) 176 switch errors.Cause(err).(type) { 177 case nil: 178 break 179 case errTLFJournalDisabled: 180 return nil, nil 181 default: 182 return nil, err 183 } 184 185 if len(ibrmds) == 0 { 186 return nil, nil 187 } 188 189 headIndex := len(ibrmds) - 1 190 head := ibrmds[headIndex] 191 if head.MergedStatus() != mStatus { 192 return nil, nil 193 } 194 195 if mStatus == kbfsmd.Unmerged && bid != kbfsmd.NullBranchID && bid != head.BID() { 196 // The given branch ID doesn't match the one in the 197 // journal, which can only be an error. 198 return nil, fmt.Errorf("Expected branch ID %s, got %s", 199 bid, head.BID()) 200 } 201 202 bareHandle, err := head.MakeBareTlfHandleWithExtra() 203 if err != nil { 204 return nil, err 205 } 206 handle, err := tlfhandle.MakeHandleWithTlfID( 207 ctx, bareHandle, id.Type(), j.jManager.config.KBPKI(), 208 j.jManager.config.KBPKI(), id, 209 j.jManager.config.OfflineAvailabilityForID(id)) 210 if err != nil { 211 return nil, err 212 } 213 handle.SetTlfID(id) 214 215 irmds := make([]ImmutableRootMetadata, 0, len(ibrmds)) 216 217 for _, ibrmd := range ibrmds { 218 irmd, err := j.convertImmutableBareRMDToIRMD( 219 ctx, ibrmd, handle, tlfJournal.uid, tlfJournal.key) 220 if err != nil { 221 return nil, err 222 } 223 224 irmds = append(irmds, irmd) 225 } 226 227 // It would be nice to cache the irmds here, but we can't because 228 // the underlying journal might have been converted to a branch 229 // since we fetched them, and we can't risk putting them in the 230 // cache with the wrong branch ID. TODO: convert them to 231 // ImmutableRootMetadata and cache them under the tlfJournal lock? 232 233 return irmds, nil 234 } 235 236 // GetIDForHandle implements the MDOps interface for journalMDOps. 237 func (j journalMDOps) GetIDForHandle( 238 ctx context.Context, handle *tlfhandle.Handle) (id tlf.ID, err error) { 239 id = handle.TlfID() 240 if id == tlf.NullID { 241 id, err = j.MDOps.GetIDForHandle(ctx, handle) 242 if err != nil || id == tlf.NullID { 243 return tlf.NullID, err 244 } 245 } 246 247 // If this handle is for a local conflict, use the fake TLF ID 248 // that was assigned to it instead. 249 newID, ok := j.jManager.getConflictIDForHandle(id, handle) 250 if ok { 251 id = newID 252 handle.SetTlfID(id) 253 } else { 254 ci := handle.ConflictInfo() 255 if ci != nil && ci.Type == tlf.HandleExtensionLocalConflict { 256 return tlf.NullID, errors.Errorf( 257 "Couldn't find local conflict handle for %s", 258 handle.GetCanonicalPath()) 259 } 260 } 261 262 // Create the journal if needed, while we have access to `handle`. 263 _, _ = j.jManager.getTLFJournal(id, handle) 264 return id, nil 265 } 266 267 // TODO: Combine the two GetForTLF functions in MDOps to avoid the 268 // need for this helper function. 269 func (j journalMDOps) getForTLF(ctx context.Context, id tlf.ID, bid kbfsmd.BranchID, 270 mStatus kbfsmd.MergeStatus, lockBeforeGet *keybase1.LockID, 271 delegateFn func(context.Context, tlf.ID, *keybase1.LockID) ( 272 ImmutableRootMetadata, error)) (ImmutableRootMetadata, error) { 273 // If the journal has a head, use that. 274 irmd, err := j.getHeadFromJournal(ctx, id, bid, mStatus, nil) 275 if err != nil { 276 return ImmutableRootMetadata{}, err 277 } 278 if irmd != (ImmutableRootMetadata{}) { 279 return irmd, nil 280 } 281 282 if mStatus == kbfsmd.Unmerged { 283 // Journal users always store their unmerged heads locally, so 284 // no need to check with the server. 285 return ImmutableRootMetadata{}, nil 286 } 287 288 // Otherwise, consult the server instead. 289 return delegateFn(ctx, id, lockBeforeGet) 290 } 291 292 func (j journalMDOps) GetForTLF( 293 ctx context.Context, id tlf.ID, lockBeforeGet *keybase1.LockID) ( 294 irmd ImmutableRootMetadata, err error) { 295 j.jManager.log.LazyTrace(ctx, "jMDOps: GetForTLF %s", id) 296 defer func() { 297 j.jManager.deferLog.LazyTrace(ctx, "jMDOps: GetForTLF %s done (err=%v)", id, err) 298 }() 299 300 return j.getForTLF( 301 ctx, id, kbfsmd.NullBranchID, kbfsmd.Merged, lockBeforeGet, j.MDOps.GetForTLF) 302 } 303 304 func (j journalMDOps) GetForTLFByTime( 305 ctx context.Context, id tlf.ID, serverTime time.Time) ( 306 ImmutableRootMetadata, error) { 307 // For now, we don't bother looking up MDs from the journal by 308 // time -- that could be confusing, since the "server time" could 309 // change once the MD is actually flushed. 310 return j.MDOps.GetForTLFByTime(ctx, id, serverTime) 311 } 312 313 func (j journalMDOps) GetUnmergedForTLF( 314 ctx context.Context, id tlf.ID, bid kbfsmd.BranchID) ( 315 irmd ImmutableRootMetadata, err error) { 316 j.jManager.log.LazyTrace(ctx, "jMDOps: GetUnmergedForTLF %s %s", id, bid) 317 defer func() { 318 j.jManager.deferLog.LazyTrace(ctx, "jMDOps: GetForTLF %s %s done (err=%v)", id, bid, err) 319 }() 320 321 delegateFn := func(ctx context.Context, id tlf.ID, _ *keybase1.LockID) ( 322 ImmutableRootMetadata, error) { 323 return j.MDOps.GetUnmergedForTLF(ctx, id, bid) 324 } 325 return j.getForTLF(ctx, id, bid, kbfsmd.Unmerged, nil, delegateFn) 326 } 327 328 // TODO: Combine the two GetRange functions in MDOps to avoid the need 329 // for this helper function. 330 func (j journalMDOps) getRange( 331 ctx context.Context, id tlf.ID, bid kbfsmd.BranchID, mStatus kbfsmd.MergeStatus, 332 start, stop kbfsmd.Revision, lockBeforeGet *keybase1.LockID, 333 delegateFn func(ctx context.Context, id tlf.ID, 334 start, stop kbfsmd.Revision, lockBeforeGet *keybase1.LockID) ( 335 []ImmutableRootMetadata, error)) ( 336 []ImmutableRootMetadata, error) { 337 // Grab the range from the journal first. 338 jirmds, err := j.getRangeFromJournal(ctx, id, bid, mStatus, start, stop) 339 switch errors.Cause(err).(type) { 340 case nil: 341 break 342 case errTLFJournalDisabled: 343 // Fall back to the server. 344 return delegateFn(ctx, id, start, stop, lockBeforeGet) 345 default: 346 return nil, err 347 } 348 349 if len(jirmds) != 0 && lockBeforeGet != nil { 350 // We need to grab locks, so we have to hit the server. But it's 351 // dangerous to bypass journal if we have revisions in this range from 352 // the journal. For now, we just return error here. 353 // NOTE: In the future if we ever need locking in places other than 354 // SyncFromServer, we can use the naked Lock RPC to grab the lock if 355 // everything we need is in the journal already. 356 return nil, errors.New( 357 "cannot lock when getting revisions that exist in journal") 358 } 359 360 // If it's empty, fall back to the server if this isn't a local 361 // squash branch. TODO: we should be able to avoid server access 362 // for regular conflict branches when the journal is enabled, as 363 // well, once we're confident that all old server-based branches 364 // have been resolved. 365 if len(jirmds) == 0 { 366 if bid == kbfsmd.PendingLocalSquashBranchID { 367 return jirmds, nil 368 } 369 return delegateFn(ctx, id, start, stop, lockBeforeGet) 370 } 371 372 // If the first revision from the journal is the first revision we 373 // asked for (or this is a local squash that doesn't require 374 // server access), then just return the range from the journal. 375 // TODO: we should be able to avoid server access for regular 376 // conflict branches, as well. 377 if jirmds[0].Revision() == start || bid == kbfsmd.PendingLocalSquashBranchID { 378 return jirmds, nil 379 } 380 381 // Otherwise, fetch the rest from the server and prepend them. 382 serverStop := jirmds[0].Revision() - 1 383 irmds, err := delegateFn(ctx, id, start, serverStop, lockBeforeGet) 384 if err != nil { 385 return nil, err 386 } 387 388 if len(irmds) == 0 { 389 return jirmds, nil 390 } 391 392 lastRev := irmds[len(irmds)-1].Revision() 393 if lastRev != serverStop { 394 return nil, fmt.Errorf( 395 "Expected last server rev %d, got %d", 396 serverStop, lastRev) 397 } 398 399 return append(irmds, jirmds...), nil 400 } 401 402 func (j journalMDOps) GetRange(ctx context.Context, id tlf.ID, start, 403 stop kbfsmd.Revision, lockBeforeGet *keybase1.LockID) ( 404 irmds []ImmutableRootMetadata, err error) { 405 j.jManager.log.LazyTrace(ctx, "jMDOps: GetRange %s %d-%d", id, start, stop) 406 defer func() { 407 j.jManager.deferLog.LazyTrace(ctx, "jMDOps: GetRange %s %d-%d done (err=%v)", id, start, stop, err) 408 }() 409 410 return j.getRange(ctx, id, kbfsmd.NullBranchID, kbfsmd.Merged, start, stop, lockBeforeGet, 411 j.MDOps.GetRange) 412 } 413 414 func (j journalMDOps) GetUnmergedRange( 415 ctx context.Context, id tlf.ID, bid kbfsmd.BranchID, 416 start, stop kbfsmd.Revision) (irmd []ImmutableRootMetadata, err error) { 417 j.jManager.log.LazyTrace(ctx, "jMDOps: GetUnmergedRange %s %d-%d", id, start, stop) 418 defer func() { 419 j.jManager.deferLog.LazyTrace(ctx, "jMDOps: GetUnmergedRange %s %d-%d done (err=%v)", id, start, stop, err) 420 }() 421 422 delegateFn := func(ctx context.Context, id tlf.ID, 423 start, stop kbfsmd.Revision, _ *keybase1.LockID) ( 424 []ImmutableRootMetadata, error) { 425 return j.MDOps.GetUnmergedRange(ctx, id, bid, start, stop) 426 } 427 return j.getRange(ctx, id, bid, kbfsmd.Unmerged, start, stop, nil, 428 delegateFn) 429 } 430 431 func (j journalMDOps) Put(ctx context.Context, rmd *RootMetadata, 432 verifyingKey kbfscrypto.VerifyingKey, 433 lc *keybase1.LockContext, priority keybase1.MDPriority, 434 bps data.BlockPutState) ( 435 irmd ImmutableRootMetadata, err error) { 436 j.jManager.log.LazyTrace(ctx, "jMDOps: Put %s %d", rmd.TlfID(), rmd.Revision()) 437 defer func() { 438 j.jManager.deferLog.LazyTrace(ctx, "jMDOps: Put %s %d done (err=%v)", rmd.TlfID(), rmd.Revision(), err) 439 }() 440 441 if tlfJournal, ok := j.jManager.getTLFJournal( 442 rmd.TlfID(), rmd.GetTlfHandle()); ok { 443 if lc != nil { 444 return ImmutableRootMetadata{}, errors.New( 445 "journal Put doesn't support LockContext " + 446 "yet. Use FinishSingleOp to require locks on MD write.") 447 } 448 if priority != keybase1.MDPriorityNormal { 449 return ImmutableRootMetadata{}, errors.New( 450 "journal Put doesn't support priority other than " + 451 "MDPriorityNormal yet. Use FinishSingleOp to specify " + 452 "priority on MD write.") 453 } 454 // Just route to the journal. 455 irmd, err := tlfJournal.putMD(ctx, rmd, verifyingKey, bps) 456 switch errors.Cause(err).(type) { 457 case nil: 458 return irmd, nil 459 case errTLFJournalDisabled: 460 break 461 default: 462 return ImmutableRootMetadata{}, err 463 } 464 } 465 466 return j.MDOps.Put(ctx, rmd, verifyingKey, lc, priority, bps) 467 } 468 469 func (j journalMDOps) PutUnmerged( 470 ctx context.Context, rmd *RootMetadata, 471 verifyingKey kbfscrypto.VerifyingKey, bps data.BlockPutState) ( 472 irmd ImmutableRootMetadata, err error) { 473 j.jManager.log.LazyTrace(ctx, "jMDOps: PutUnmerged %s %d", rmd.TlfID(), rmd.Revision()) 474 defer func() { 475 j.jManager.deferLog.LazyTrace(ctx, "jMDOps: PutUnmerged %s %d done (err=%v)", rmd.TlfID(), rmd.Revision(), err) 476 }() 477 478 if tlfJournal, ok := j.jManager.getTLFJournal( 479 rmd.TlfID(), rmd.GetTlfHandle()); ok { 480 rmd.SetUnmerged() 481 irmd, err := tlfJournal.putMD(ctx, rmd, verifyingKey, bps) 482 switch errors.Cause(err).(type) { 483 case nil: 484 return irmd, nil 485 case errTLFJournalDisabled: 486 break 487 default: 488 return ImmutableRootMetadata{}, err 489 } 490 } 491 492 return j.MDOps.PutUnmerged(ctx, rmd, verifyingKey, bps) 493 } 494 495 func (j journalMDOps) PruneBranch( 496 ctx context.Context, id tlf.ID, bid kbfsmd.BranchID) (err error) { 497 j.jManager.log.LazyTrace(ctx, "jMDOps: PruneBranch %s %s", id, bid) 498 defer func() { 499 j.jManager.deferLog.LazyTrace(ctx, "jMDOps: PruneBranch %s %s (err=%v)", id, bid, err) 500 }() 501 502 if tlfJournal, ok := j.jManager.getTLFJournal(id, nil); ok { 503 // Prune the journal, too. 504 err := tlfJournal.clearMDs(ctx, bid) 505 switch errors.Cause(err).(type) { 506 case nil: 507 break 508 case errTLFJournalDisabled: 509 break 510 default: 511 return err 512 } 513 } 514 515 return j.MDOps.PruneBranch(ctx, id, bid) 516 } 517 518 func (j journalMDOps) ResolveBranch( 519 ctx context.Context, id tlf.ID, bid kbfsmd.BranchID, 520 blocksToDelete []kbfsblock.ID, rmd *RootMetadata, 521 verifyingKey kbfscrypto.VerifyingKey, bps data.BlockPutState) ( 522 irmd ImmutableRootMetadata, err error) { 523 j.jManager.log.LazyTrace(ctx, "jMDOps: ResolveBranch %s %s", id, bid) 524 defer func() { 525 j.jManager.deferLog.LazyTrace(ctx, "jMDOps: ResolveBranch %s %s (err=%v)", id, bid, err) 526 }() 527 528 if tlfJournal, ok := j.jManager.getTLFJournal(id, rmd.GetTlfHandle()); ok { 529 irmd, err := tlfJournal.resolveBranch( 530 ctx, bid, blocksToDelete, rmd, verifyingKey, bps) 531 switch errors.Cause(err).(type) { 532 case nil: 533 return irmd, nil 534 case errTLFJournalDisabled: 535 break 536 default: 537 return ImmutableRootMetadata{}, err 538 } 539 } 540 541 return j.MDOps.ResolveBranch( 542 ctx, id, bid, blocksToDelete, rmd, verifyingKey, bps) 543 }