diff --git a/src/add-ons/kernel/file_systems/bfs/BlockAllocator.cpp b/src/add-ons/kernel/file_systems/bfs/BlockAllocator.cpp index 79fff024c2..f714ba360e 100644 --- a/src/add-ons/kernel/file_systems/bfs/BlockAllocator.cpp +++ b/src/add-ons/kernel/file_systems/bfs/BlockAllocator.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2001-2020, Axel Dörfler, axeld@pinc-software.de. + * Copyright 2001-2025, Axel Dörfler, axeld@pinc-software.de. * This file may be used under the terms of the MIT License. */ @@ -1112,6 +1112,46 @@ BlockAllocator::Allocate(Transaction& transaction, Inode* inode, } +/*! Attempts to allocate a specific block run. +*/ +status_t +BlockAllocator::AllocateBlockRun(Transaction& transaction, block_run run) +{ + RecursiveLocker lock(fLock); + + if (run.AllocationGroup() >= fNumGroups) + return B_BAD_VALUE; + + if (!IsCompletelyInsideAllowedRange(run)) + return B_DEVICE_FULL; + + uint32 bitsPerBlock = fVolume->BlockSize() << 3; + + AllocationGroup& group = fGroups[run.AllocationGroup()]; + AllocationBlock cached(fVolume); + + int32 end = run.Start() + run.Length(); + + // check that the requested blocks are free + for (int32 block = run.Start(); block < end; block++) { + if (cached.SetTo(group, block / bitsPerBlock) < B_OK) + RETURN_ERROR(B_ERROR); + + if (cached.IsUsed(block % bitsPerBlock)) + return B_DEVICE_FULL; + } + + status_t status = group.Allocate(transaction, run.Start(), run.Length()); + if (status != B_OK) + return status; + + fVolume->SuperBlock().used_blocks + = HOST_ENDIAN_TO_BFS_INT64(fVolume->UsedBlocks() + run.Length()); + + return B_OK; +} + + status_t BlockAllocator::Free(Transaction& transaction, block_run run) { diff --git a/src/add-ons/kernel/file_systems/bfs/BlockAllocator.h b/src/add-ons/kernel/file_systems/bfs/BlockAllocator.h index 08342d418c..685de6cae0 100644 --- a/src/add-ons/kernel/file_systems/bfs/BlockAllocator.h +++ b/src/add-ons/kernel/file_systems/bfs/BlockAllocator.h @@ -41,11 +41,14 @@ public: status_t Allocate(Transaction& transaction, Inode* inode, off_t numBlocks, block_run& run, uint16 minimum = 1); - status_t Free(Transaction& transaction, block_run run); status_t AllocateBlocks(Transaction& transaction, int32 group, uint16 start, uint16 numBlocks, uint16 minimum, block_run& run); + status_t AllocateBlockRun(Transaction& transaction, + block_run run); + + status_t Free(Transaction& transaction, block_run run); status_t Trim(uint64 offset, uint64 size, uint64& trimmedSize); diff --git a/src/add-ons/kernel/file_systems/bfs/Journal.cpp b/src/add-ons/kernel/file_systems/bfs/Journal.cpp index 33b91bbd67..93e79987fb 100644 --- a/src/add-ons/kernel/file_systems/bfs/Journal.cpp +++ b/src/add-ons/kernel/file_systems/bfs/Journal.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2001-2020, Axel Dörfler, axeld@pinc-software.de. + * Copyright 2001-2025, Axel Dörfler, axeld@pinc-software.de. * This file may be used under the terms of the MIT License. */ @@ -924,17 +924,19 @@ Journal::_WriteTransactionToLog() /*! Flushes the current log entry to disk. If \a flushBlocks is \c true it will - also write back all dirty blocks for this volume. + also write back all dirty blocks for this volume. If \a movingLog is \c + true, we allow the lock to be held when the function is called. */ status_t -Journal::_FlushLog(bool canWait, bool flushBlocks) +Journal::_FlushLog(bool canWait, bool flushBlocks, bool movingLog) { status_t status = canWait ? recursive_lock_lock(&fLock) : recursive_lock_trylock(&fLock); if (status != B_OK) return status; - if (recursive_lock_get_recursion(&fLock) > 1) { + int32 allowedLocks = movingLog ? 2 : 1; + if (recursive_lock_get_recursion(&fLock) > allowedLocks) { // whoa, FlushLogAndBlocks() was called from inside a transaction recursive_lock_unlock(&fLock); return B_OK; @@ -1097,6 +1099,111 @@ Journal::_TransactionDone(bool success) } +status_t +Journal::MoveLog(block_run newLog) +{ + block_run oldLog = fVolume->Log(); + if (newLog == oldLog) + return B_OK; + + off_t newEnd = newLog.Start() + newLog.Length(); + off_t oldEnd = oldLog.Start() + oldLog.Length(); + + // make sure the new log position is ok + if (newLog.AllocationGroup() != 0) + return B_BAD_VALUE; + + if (fVolume->ValidateBlockRun(newLog) != B_OK) + return B_BAD_VALUE; + + if (newLog.Start() < 1 + fVolume->NumBitmapBlocks()) + return B_BAD_VALUE; + + if (newEnd > fVolume->NumBlocks()) + return B_BAD_VALUE; + + status_t status; + block_run allocatedRun = {}; + + BlockAllocator& allocator = fVolume->Allocator(); + + // allocate blocks if necessary + if (newEnd > oldEnd) { + if (oldEnd > newLog.Start()) + allocatedRun.SetTo(newLog.AllocationGroup(), oldEnd, newEnd - oldEnd); + else + allocatedRun = newLog; + + Transaction transaction(fVolume, 0); + + status = allocator.AllocateBlockRun(transaction, allocatedRun); + if (status != B_OK) { + FATAL(("MoveLog: Could not allocate space to move log area!\n")); + return status; + } + + status = transaction.Done(); + if (status != B_OK) + return status; + } + + RecursiveLocker locker(fLock); + + status = _FlushLog(true, true, true); + if (status != B_OK) + return status; + + MutexLocker volumeLock(fVolume->Lock()); + + // update references to the log location and size + fVolume->SuperBlock().log_blocks = newLog; + status = fVolume->WriteSuperBlock(); + if (status != B_OK) { + fVolume->SuperBlock().log_blocks = oldLog; + + // if we had to allocate some blocks, try to free them + if (!allocatedRun.IsZero()) { + Transaction transaction(fVolume, 0); + status_t freeStatus = allocator.Free(transaction, allocatedRun); + if (freeStatus == B_OK) + freeStatus = transaction.Done(); + + // don't really care if we fail + if (freeStatus != B_OK) + REPORT_ERROR(freeStatus); + } + + return status; + } + + fLogSize = newLog.Length(); + fMaxTransactionSize = fLogSize / 2 - 5; + + volumeLock.Unlock(); + locker.Unlock(); + + // at this point, the log is moved and functional in its new location + + // free blocks if necessary + if (newEnd < oldEnd) { + block_run runToFree = block_run::Run(0, newEnd, oldEnd - newEnd); + + Transaction transaction(fVolume, 0); + + status = allocator.Free(transaction, runToFree); + if (status == B_OK) + status = transaction.Done(); + + // we've already moved the log, no sense in failing just because we + // couldn't free a couple of blocks + if (status != B_OK) + REPORT_ERROR(status); + } + + return B_OK; +} + + // #pragma mark - debugger commands diff --git a/src/add-ons/kernel/file_systems/bfs/Journal.h b/src/add-ons/kernel/file_systems/bfs/Journal.h index 3d71a42ac4..3824423425 100644 --- a/src/add-ons/kernel/file_systems/bfs/Journal.h +++ b/src/add-ons/kernel/file_systems/bfs/Journal.h @@ -41,6 +41,8 @@ public: inline uint32 FreeLogBlocks() const; + status_t MoveLog(block_run newLog); + #ifdef BFS_DEBUGGER_COMMANDS void Dump(); #endif @@ -49,7 +51,8 @@ private: bool _HasSubTransaction() const { return fHasSubtransaction; } - status_t _FlushLog(bool canWait, bool flushBlocks); + status_t _FlushLog(bool canWait, bool flushBlocks, + bool movingLog = false); uint32 _TransactionSize() const; status_t _WriteTransactionToLog(); status_t _CheckRunArray(const run_array* array);