1 /* Journal - transaction and logging 2 * 3 * Copyright 2001-2005, Axel Dörfler, axeld@pinc-software.de. 4 * This file may be used under the terms of the MIT License. 5 */ 6 7 8 #include "Journal.h" 9 #include "Inode.h" 10 #include "Debug.h" 11 12 #include <Drivers.h> 13 #include <util/kernel_cpp.h> 14 #include <util/Stack.h> 15 #include <errno.h> 16 17 18 struct run_array { 19 int32 count; 20 union { 21 int32 max_runs; 22 int32 block_count; 23 }; 24 block_run runs[0]; 25 26 int32 CountRuns() const { return BFS_ENDIAN_TO_HOST_INT32(count); } 27 int32 MaxRuns() const { return BFS_ENDIAN_TO_HOST_INT32(max_runs) - 1; } 28 // that -1 accounts for an off-by-one error in Be's BFS implementation 29 const block_run &RunAt(int32 i) const { return runs[i]; } 30 31 static int32 MaxRuns(int32 blockSize) 32 { return (blockSize - sizeof(run_array)) / sizeof(block_run); } 33 }; 34 35 class RunArrays { 36 public: 37 RunArrays(Journal *journal); 38 ~RunArrays(); 39 40 uint32 Length() const { return fLength; } 41 42 status_t Insert(off_t blockNumber); 43 44 run_array *ArrayAt(int32 i) { return fArrays.Array()[i]; } 45 int32 CountArrays() const { return fArrays.CountItems(); } 46 47 int32 MaxArrayLength(); 48 void PrepareForWriting(); 49 50 private: 51 status_t _AddArray(); 52 bool _ContainsRun(block_run &run); 53 bool _AddRun(block_run &run); 54 55 Journal *fJournal; 56 uint32 fLength; 57 Stack<run_array *> fArrays; 58 run_array *fLastArray; 59 }; 60 61 class LogEntry : public DoublyLinkedListLinkImpl<LogEntry> { 62 public: 63 LogEntry(Journal *journal, uint32 logStart, uint32 length); 64 ~LogEntry(); 65 66 uint32 Start() const { return fStart; } 67 uint32 Length() const { return fLength; } 68 69 Journal *GetJournal() { return fJournal; } 70 71 private: 72 Journal *fJournal; 73 uint32 fStart; 74 uint32 fLength; 75 }; 76 77 78 // #pragma mark - 79 80 81 static void 82 add_to_iovec(iovec *vecs, int32 &index, int32 max, const void *address, size_t size) 83 { 84 if (index > 0 85 && (addr_t)vecs[index - 1].iov_base + vecs[index - 1].iov_len == (addr_t)address) { 86 // the iovec can be combined with the previous one 87 vecs[index - 1].iov_len += size; 88 return; 89 } 90 91 if (index == max) 92 panic("no more space for iovecs!"); 93 94 // we need to start a new iovec 95 vecs[index].iov_base = const_cast<void *>(address); 96 vecs[index].iov_len = size; 97 index++; 98 } 99 100 101 // #pragma mark - 102 103 104 LogEntry::LogEntry(Journal *journal, uint32 start, uint32 length) 105 : 106 fJournal(journal), 107 fStart(start), 108 fLength(length) 109 { 110 } 111 112 113 LogEntry::~LogEntry() 114 { 115 } 116 117 118 // #pragma mark - 119 120 121 RunArrays::RunArrays(Journal *journal) 122 : 123 fJournal(journal), 124 fLength(0), 125 fArrays(), 126 fLastArray(NULL) 127 { 128 } 129 130 131 RunArrays::~RunArrays() 132 { 133 run_array *array; 134 while (fArrays.Pop(&array)) 135 free(array); 136 } 137 138 139 bool 140 RunArrays::_ContainsRun(block_run &run) 141 { 142 for (int32 i = 0; i < CountArrays(); i++) { 143 run_array *array = ArrayAt(i); 144 145 for (int32 j = 0; j < array->CountRuns(); j++) { 146 block_run &arrayRun = array->runs[j]; 147 if (run.AllocationGroup() != arrayRun.AllocationGroup()) 148 continue; 149 150 if (run.Start() >= arrayRun.Start() 151 && run.Start() + run.Length() <= arrayRun.Start() + arrayRun.Length()) 152 return true; 153 } 154 } 155 156 return false; 157 } 158 159 160 /** Adds the specified block_run into the array. 161 * Note: it doesn't support overlapping - it must only be used 162 * with block_runs of length 1! 163 */ 164 165 bool 166 RunArrays::_AddRun(block_run &run) 167 { 168 ASSERT(run.length == 1); 169 170 // Be's BFS log replay routine can only deal with block_runs of size 1 171 // A pity, isn't it? Too sad we have to be compatible. 172 #if 0 173 // search for an existing adjacent block_run 174 // ToDo: this could be improved by sorting and a binary search 175 176 for (int32 i = 0; i < CountArrays(); i++) { 177 run_array *array = ArrayAt(i); 178 179 for (int32 j = 0; j < array->CountRuns(); j++) { 180 block_run &arrayRun = array->runs[j]; 181 if (run.AllocationGroup() != arrayRun.AllocationGroup()) 182 continue; 183 184 if (run.Start() == arrayRun.Start() + arrayRun.Length()) { 185 // matches the end 186 arrayRun.length = HOST_ENDIAN_TO_BFS_INT16(arrayRun.Length() + 1); 187 array->block_count++; 188 fLength++; 189 return true; 190 } else if (run.start + 1 == arrayRun.start) { 191 // matches the start 192 arrayRun.start = run.start; 193 arrayRun.length = HOST_ENDIAN_TO_BFS_INT16(arrayRun.Length() + 1); 194 array->block_count++; 195 fLength++; 196 return true; 197 } 198 } 199 } 200 #endif 201 202 // no entry found, add new to the last array 203 204 if (fLastArray == NULL || fLastArray->CountRuns() == fLastArray->MaxRuns()) 205 return false; 206 207 fLastArray->runs[fLastArray->CountRuns()] = run; 208 fLastArray->count = HOST_ENDIAN_TO_BFS_INT16(fLastArray->CountRuns() + 1); 209 fLastArray->block_count++; 210 fLength++; 211 return true; 212 } 213 214 215 status_t 216 RunArrays::_AddArray() 217 { 218 int32 blockSize = fJournal->GetVolume()->BlockSize(); 219 run_array *array = (run_array *)malloc(blockSize); 220 if (array == NULL) 221 return B_NO_MEMORY; 222 223 if (fArrays.Push(array) != B_OK) { 224 free(array); 225 return B_NO_MEMORY; 226 } 227 228 memset(array, 0, blockSize); 229 array->block_count = 1; 230 fLastArray = array; 231 fLength++; 232 233 return B_OK; 234 } 235 236 237 status_t 238 RunArrays::Insert(off_t blockNumber) 239 { 240 Volume *volume = fJournal->GetVolume(); 241 block_run run = volume->ToBlockRun(blockNumber); 242 243 if (fLastArray != NULL) { 244 // check if the block is already in the array 245 if (_ContainsRun(run)) 246 return B_OK; 247 } 248 249 // insert block into array 250 251 if (!_AddRun(run)) { 252 // array is full 253 if (_AddArray() != B_OK) 254 return B_NO_MEMORY; 255 256 // insert entry manually, because _AddRun() would search the 257 // all arrays again for a free spot 258 fLastArray->runs[0] = run; 259 fLastArray->count = HOST_ENDIAN_TO_BFS_INT16(1); 260 fLastArray->block_count++; 261 fLength++; 262 } 263 264 return B_OK; 265 } 266 267 268 int32 269 RunArrays::MaxArrayLength() 270 { 271 int32 max = 0; 272 for (int32 i = 0; i < CountArrays(); i++) { 273 if (ArrayAt(i)->block_count > max) 274 max = ArrayAt(i)->block_count; 275 } 276 277 return max; 278 } 279 280 281 void 282 RunArrays::PrepareForWriting() 283 { 284 int32 blockSize = fJournal->GetVolume()->BlockSize(); 285 286 for (int32 i = 0; i < CountArrays(); i++) { 287 ArrayAt(i)->max_runs = HOST_ENDIAN_TO_BFS_INT32(run_array::MaxRuns(blockSize)); 288 } 289 } 290 291 292 // #pragma mark - 293 294 295 Journal::Journal(Volume *volume) 296 : 297 fVolume(volume), 298 fLock("bfs journal"), 299 fOwner(NULL), 300 fLogSize(volume->Log().length), 301 fMaxTransactionSize(fLogSize / 4 - 5), 302 fUsed(0), 303 fUnwrittenTransactions(0) 304 { 305 if (fMaxTransactionSize > fLogSize / 2) 306 fMaxTransactionSize = fLogSize / 2 - 5; 307 } 308 309 310 Journal::~Journal() 311 { 312 FlushLogAndBlocks(); 313 } 314 315 316 status_t 317 Journal::InitCheck() 318 { 319 if (fVolume->LogStart() != fVolume->LogEnd()) { 320 if (fVolume->SuperBlock().flags != SUPER_BLOCK_DISK_DIRTY) 321 FATAL(("log_start and log_end differ, but disk is marked clean - trying to replay log...\n")); 322 323 return ReplayLog(); 324 } 325 326 return B_OK; 327 } 328 329 330 status_t 331 Journal::_CheckRunArray(const run_array *array) 332 { 333 int32 maxRuns = run_array::MaxRuns(fVolume->BlockSize()); 334 if (array->MaxRuns() != maxRuns 335 || array->CountRuns() > maxRuns 336 || array->CountRuns() <= 0) { 337 FATAL(("Log entry has broken header!\n")); 338 return B_ERROR; 339 } 340 341 for (int32 i = 0; i < array->CountRuns(); i++) { 342 if (fVolume->ValidateBlockRun(array->RunAt(i)) != B_OK) 343 return B_ERROR; 344 } 345 346 PRINT(("Log entry has %ld entries (%Ld)\n", array->CountRuns())); 347 return B_OK; 348 } 349 350 351 /** Replays an entry in the log. 352 * \a _start points to the entry in the log, and will be bumped to the next 353 * one if replaying succeeded. 354 */ 355 356 status_t 357 Journal::_ReplayRunArray(int32 *_start) 358 { 359 PRINT(("ReplayRunArray(start = %ld)\n", *_start)); 360 361 off_t logOffset = fVolume->ToBlock(fVolume->Log()); 362 off_t blockNumber = *_start % fLogSize; 363 int32 blockSize = fVolume->BlockSize(); 364 int32 count = 1; 365 366 CachedBlock cachedArray(fVolume); 367 368 const run_array *array = (const run_array *)cachedArray.SetTo(logOffset + blockNumber); 369 if (array == NULL) 370 return B_IO_ERROR; 371 372 if (_CheckRunArray(array) < B_OK) 373 return B_BAD_DATA; 374 375 blockNumber = (blockNumber + 1) % fLogSize; 376 377 CachedBlock cached(fVolume); 378 for (int32 index = 0; index < array->CountRuns(); index++) { 379 const block_run &run = array->RunAt(index); 380 PRINT(("replay block run %lu:%u:%u in log at %Ld!\n", run.AllocationGroup(), 381 run.Start(), run.Length(), blockNumber)); 382 383 off_t offset = fVolume->ToOffset(run); 384 for (int32 i = 0; i < run.Length(); i++) { 385 const uint8 *data = cached.SetTo(logOffset + blockNumber); 386 if (data == NULL) 387 RETURN_ERROR(B_IO_ERROR); 388 389 ssize_t written = write_pos(fVolume->Device(), 390 offset + (i * blockSize), data, blockSize); 391 if (written != blockSize) 392 RETURN_ERROR(B_IO_ERROR); 393 394 blockNumber = (blockNumber + 1) % fLogSize; 395 count++; 396 } 397 } 398 399 *_start += count; 400 return B_OK; 401 } 402 403 404 /** Replays all log entries - this will put the disk into a 405 * consistent and clean state, if it was not correctly unmounted 406 * before. 407 * This method is called by Journal::InitCheck() if the log start 408 * and end pointer don't match. 409 */ 410 411 status_t 412 Journal::ReplayLog() 413 { 414 INFORM(("Replay log, disk was not correctly unmounted...\n")); 415 416 int32 start = fVolume->LogStart(); 417 int32 lastStart = -1; 418 while (true) { 419 // stop if the log is completely flushed 420 if (start == fVolume->LogEnd()) 421 break; 422 423 if (start == lastStart) { 424 // strange, flushing the log hasn't changed the log_start pointer 425 return B_ERROR; 426 } 427 lastStart = start; 428 429 status_t status = _ReplayRunArray(&start); 430 if (status < B_OK) { 431 FATAL(("replaying log entry from %ld failed: %s\n", start, strerror(status))); 432 return B_ERROR; 433 } 434 start = start % fLogSize; 435 } 436 437 PRINT(("replaying worked fine!\n")); 438 fVolume->SuperBlock().log_start = fVolume->LogEnd(); 439 fVolume->LogStart() = fVolume->LogEnd(); 440 fVolume->SuperBlock().flags = SUPER_BLOCK_DISK_CLEAN; 441 442 return fVolume->WriteSuperBlock(); 443 } 444 445 446 /** This is a callback function that is called by the cache, whenever 447 * a block is flushed to disk that was updated as part of a transaction. 448 * This is necessary to keep track of completed transactions, to be 449 * able to update the log start pointer. 450 */ 451 452 void 453 Journal::_blockNotify(int32 transactionID, void *arg) 454 { 455 LogEntry *logEntry = (LogEntry *)arg; 456 457 PRINT(("Log entry %p has been finished, transaction ID = %ld\n", logEntry, transactionID)); 458 459 Journal *journal = logEntry->GetJournal(); 460 disk_super_block &superBlock = journal->fVolume->SuperBlock(); 461 bool update = false; 462 463 // Set log_start pointer if possible... 464 465 journal->fEntriesLock.Lock(); 466 467 if (logEntry == journal->fEntries.First()) { 468 LogEntry *next = journal->fEntries.GetNext(logEntry); 469 if (next != NULL) { 470 int32 length = next->Start() - logEntry->Start(); 471 // log entries inbetween could have been already released, so 472 // we can't just use LogEntry::Length() here 473 superBlock.log_start = (superBlock.log_start + length) % journal->fLogSize; 474 } else 475 superBlock.log_start = journal->fVolume->LogEnd(); 476 477 update = true; 478 } 479 480 journal->fUsed -= logEntry->Length(); 481 journal->fEntries.Remove(logEntry); 482 journal->fEntriesLock.Unlock(); 483 484 delete logEntry; 485 486 // update the super block, and change the disk's state, if necessary 487 488 if (update) { 489 journal->fVolume->LogStart() = superBlock.log_start; 490 491 if (superBlock.log_start == superBlock.log_end) 492 superBlock.flags = SUPER_BLOCK_DISK_CLEAN; 493 494 status_t status = journal->fVolume->WriteSuperBlock(); 495 if (status != B_OK) { 496 FATAL(("blockNotify: could not write back super block: %s\n", 497 strerror(status))); 498 } 499 } 500 } 501 502 503 status_t 504 Journal::_WriteTransactionToLog() 505 { 506 // ToDo: in case of a failure, we need a backup plan like writing all 507 // changed blocks back to disk immediately 508 509 fUnwrittenTransactions = 0; 510 511 int32 blockShift = fVolume->BlockShift(); 512 off_t logOffset = fVolume->ToBlock(fVolume->Log()) << blockShift; 513 off_t logStart = fVolume->LogEnd(); 514 off_t logPosition = logStart % fLogSize; 515 status_t status; 516 517 // create run_array structures for all changed blocks 518 519 RunArrays runArrays(this); 520 521 uint32 cookie = 0; 522 off_t blockNumber; 523 while (cache_next_block_in_transaction(fVolume->BlockCache(), fTransactionID, 524 &cookie, &blockNumber, NULL, NULL) == B_OK) { 525 status = runArrays.Insert(blockNumber); 526 if (status < B_OK) { 527 FATAL(("filling log entry failed!")); 528 return status; 529 } 530 } 531 532 if (runArrays.Length() == 0) { 533 // nothing has changed during this transaction 534 cache_end_transaction(fVolume->BlockCache(), fTransactionID, NULL, NULL); 535 return B_OK; 536 } 537 538 // Make sure there is enough space in the log. 539 // If that fails for whatever reason, panic! 540 // ToDo: 541 /* force_cache_flush(fVolume->Device(), false); 542 int32 tries = fLogSize / 2 + 1; 543 while (TransactionSize() > FreeLogBlocks() && tries-- > 0) 544 force_cache_flush(fVolume->Device(), true); 545 546 if (tries <= 0) { 547 fVolume->Panic(); 548 return B_BAD_DATA; 549 } 550 */ 551 552 // Write log entries to disk 553 554 int32 maxVecs = runArrays.MaxArrayLength(); 555 556 iovec *vecs = (iovec *)malloc(sizeof(iovec) * maxVecs); 557 if (vecs == NULL) { 558 // ToDo: write back log entries directly? 559 return B_NO_MEMORY; 560 } 561 562 runArrays.PrepareForWriting(); 563 564 for (int32 k = 0; k < runArrays.CountArrays(); k++) { 565 run_array *array = runArrays.ArrayAt(k); 566 int32 index = 0, count = 1; 567 int32 wrap = fLogSize - logStart; 568 569 add_to_iovec(vecs, index, maxVecs, (void *)array, fVolume->BlockSize()); 570 571 // add block runs 572 573 for (int32 i = 0; i < array->CountRuns(); i++) { 574 const block_run &run = array->RunAt(i); 575 off_t blockNumber = fVolume->ToBlock(run); 576 577 for (int32 j = 0; j < run.Length(); j++) { 578 if (count >= wrap) { 579 // we need to write back the first half of the entry directly 580 logPosition = logStart + count; 581 if (writev_pos(fVolume->Device(), logOffset 582 + (logStart << blockShift), vecs, index) < 0) 583 FATAL(("could not write log area!\n")); 584 585 logStart = 0; 586 wrap = fLogSize; 587 count = 0; 588 index = 0; 589 } 590 591 // make blocks available in the cache 592 const void *data; 593 if (j == 0) { 594 data = block_cache_get_etc(fVolume->BlockCache(), blockNumber, 595 blockNumber, run.Length()); 596 } else 597 data = block_cache_get(fVolume->BlockCache(), blockNumber + j); 598 599 if (data == NULL) 600 return B_IO_ERROR; 601 602 add_to_iovec(vecs, index, maxVecs, data, fVolume->BlockSize()); 603 count++; 604 } 605 } 606 607 // write back log entry 608 if (count > 0) { 609 logPosition = logStart + count; 610 if (writev_pos(fVolume->Device(), logOffset + (logStart << blockShift), 611 vecs, index) < 0) 612 FATAL(("could not write log area: %s!\n", strerror(errno))); 613 } 614 615 // release blocks again 616 for (int32 i = 0; i < array->CountRuns(); i++) { 617 const block_run &run = array->RunAt(i); 618 off_t blockNumber = fVolume->ToBlock(run); 619 620 for (int32 j = 0; j < run.Length(); j++) { 621 block_cache_put(fVolume->BlockCache(), blockNumber + j); 622 } 623 } 624 } 625 626 LogEntry *logEntry = new LogEntry(this, fVolume->LogEnd(), runArrays.Length()); 627 if (logEntry == NULL) { 628 FATAL(("no memory to allocate log entries!")); 629 return B_NO_MEMORY; 630 } 631 632 // Update the log end pointer in the super block 633 634 fVolume->SuperBlock().flags = SUPER_BLOCK_DISK_DIRTY; 635 fVolume->SuperBlock().log_end = logPosition; 636 fVolume->LogEnd() = logPosition; 637 638 status = fVolume->WriteSuperBlock(); 639 640 // We need to flush the drives own cache here to ensure 641 // disk consistency. 642 // If that call fails, we can't do anything about it anyway 643 ioctl(fVolume->Device(), B_FLUSH_DRIVE_CACHE); 644 645 // at this point, we can finally end the transaction - we're in 646 // a guaranteed valid state 647 648 fEntriesLock.Lock(); 649 fEntries.Add(logEntry); 650 fUsed += logEntry->Length(); 651 fEntriesLock.Unlock(); 652 653 cache_end_transaction(fVolume->BlockCache(), fTransactionID, _blockNotify, logEntry); 654 655 // If the log goes to the next round (the log is written as a 656 // circular buffer), all blocks will be flushed out which is 657 // possible because we don't have any locked blocks at this 658 // point. 659 if (logPosition < logStart) 660 fVolume->FlushDevice(); 661 662 return status; 663 } 664 665 666 status_t 667 Journal::FlushLogAndBlocks() 668 { 669 status_t status = fLock.Lock(); 670 if (status != B_OK) 671 return status; 672 673 if (fLock.OwnerCount() > 1) { 674 // whoa, FlushLogAndBlocks() was called from inside a transaction 675 fLock.Unlock(); 676 return B_OK; 677 } 678 679 // write the current log entry to disk 680 681 if (fUnwrittenTransactions != 0 && _TransactionSize() != 0) { 682 status = _WriteTransactionToLog(); 683 if (status < B_OK) 684 FATAL(("writing current log entry failed: %s\n", strerror(status))); 685 } 686 687 status = fVolume->FlushDevice(); 688 689 fLock.Unlock(); 690 return status; 691 } 692 693 694 status_t 695 Journal::Lock(Transaction *owner) 696 { 697 status_t status = fLock.Lock(); 698 if (status != B_OK) 699 return status; 700 701 /* ToDo: 702 // if the last transaction is older than 2 secs, start a new one 703 if (fTransactionsInEntry != 0 && system_time() - fTimestamp > 2000000L) 704 WriteLogEntry(); 705 */ 706 707 if (fLock.OwnerCount() > 1) { 708 // we'll just use the current transaction again 709 return B_OK; 710 } 711 712 fOwner = owner; 713 714 // ToDo: we need a way to find out how big the current transaction is; 715 // we need to be able to either detach the latest sub transaction on 716 // demand, as well as having some kind of fall back plan in case the 717 // sub transaction itself grows bigger than the log. 718 // For that, it would be nice to have some call-back interface in the 719 // cache transaction API... 720 721 if (fUnwrittenTransactions > 0) { 722 // start a sub transaction 723 cache_start_sub_transaction(fVolume->BlockCache(), fTransactionID); 724 } else 725 fTransactionID = cache_start_transaction(fVolume->BlockCache()); 726 727 if (fTransactionID < B_OK) { 728 fLock.Unlock(); 729 return fTransactionID; 730 } 731 732 return B_OK; 733 } 734 735 736 void 737 Journal::Unlock(Transaction *owner, bool success) 738 { 739 if (fLock.OwnerCount() == 1) { 740 // we only end the transaction if we would really unlock it 741 // ToDo: what about failing transactions that do not unlock? 742 _TransactionDone(success); 743 744 fTimestamp = system_time(); 745 fOwner = NULL; 746 } 747 748 fLock.Unlock(); 749 } 750 751 752 uint32 753 Journal::_TransactionSize() const 754 { 755 int32 count = cache_blocks_in_transaction(fVolume->BlockCache(), fTransactionID); 756 if (count < 0) 757 return 0; 758 759 return count; 760 } 761 762 763 status_t 764 Journal::_TransactionDone(bool success) 765 { 766 if (!success) { 767 if (_HasSubTransaction()) 768 cache_abort_sub_transaction(fVolume->BlockCache(), fTransactionID); 769 else 770 cache_abort_transaction(fVolume->BlockCache(), fTransactionID); 771 772 return B_OK; 773 } 774 775 // Up to a maximum size, we will just batch several 776 // transactions together to improve speed 777 if (_TransactionSize() < fMaxTransactionSize) { 778 fUnwrittenTransactions++; 779 return B_OK; 780 } 781 782 return _WriteTransactionToLog(); 783 } 784 785 786 status_t 787 Journal::LogBlocks(off_t blockNumber, const uint8 *buffer, size_t numBlocks) 788 { 789 panic("LogBlocks() called!\n"); 790 #if 0 791 // ToDo: that's for now - we should change the log file size here 792 if (TransactionSize() + numBlocks + 1 > fLogSize) 793 return B_DEVICE_FULL; 794 795 int32 blockSize = fVolume->BlockSize(); 796 797 for (;numBlocks-- > 0; blockNumber++, buffer += blockSize) { 798 if (fArray.Find(blockNumber) >= 0) { 799 // The block is already in the log, so just update its data 800 // Note, this is only necessary if this method is called with a buffer 801 // different from the cached block buffer - which is unlikely but 802 // we'll make sure this way (costs one cache lookup, though). 803 // ToDo: 804 /* status_t status = cached_write(fVolume->Device(), blockNumber, buffer, 1, blockSize); 805 if (status < B_OK) 806 return status; 807 */ 808 continue; 809 } 810 811 // Insert the block into the transaction's array, and write the changes 812 // back into the locked cache buffer 813 fArray.Insert(blockNumber); 814 815 // ToDo: 816 /* status_t status = cached_write_locked(fVolume->Device(), blockNumber, buffer, 1, blockSize); 817 if (status < B_OK) 818 return status; 819 */ } 820 821 // ToDo: 822 // If necessary, flush the log, so that we have enough space for this transaction 823 /* if (TransactionSize() > FreeLogBlocks()) 824 force_cache_flush(fVolume->Device(), true); 825 */ 826 #endif 827 return B_OK; 828 } 829 830 831 // #pragma mark - 832 833 834 status_t 835 Transaction::Start(Volume *volume, off_t refBlock) 836 { 837 // has it already been started? 838 if (fJournal != NULL) 839 return B_OK; 840 841 fJournal = volume->GetJournal(refBlock); 842 if (fJournal != NULL && fJournal->Lock(this) == B_OK) 843 return B_OK; 844 845 fJournal = NULL; 846 return B_ERROR; 847 } 848 849