bfs: Factor the code to add a block_run to a data_stream out of _GrowStream()

This is done so the code can be reused when moving the file stream.

Change-Id: I4c31c14492904cf8d490ec3720450e3b54f00cbf
Reviewed-on: https://review.haiku-os.org/c/haiku/+/927
Tested-by: Commit checker robot <[email protected]>
Reviewed-by: waddlesplash <[email protected]>
This commit is contained in:
Andreas Henriksson
2026-01-22 16:57:47 +00:00
committed by Axel Dörfler
parent e93c2b5609
commit f6829b6ccf
2 changed files with 281 additions and 228 deletions
+277 -228
View File
@@ -1715,6 +1715,254 @@ Inode::_AllocateBlockArray(Transaction& transaction, block_run& run,
}
/*! Adds \a run to \a data, allocating indirection blocks if necessary.
If the block run cannot be added as is, due to constraints on block run
size in the double indirect range, \a rest is set to the number of blocks
that need to be shaved off the run and the function returns.
If the physical stream size ends up larger than \a targetSize, the stream
size is set to the target file size.
*/
status_t
Inode::_AddBlockRun(Transaction& transaction, data_stream* data, block_run run,
off_t targetSize, int32* rest, off_t beginBlock, off_t endBlock)
{
status_t status;
if (rest)
*rest = 0;
bool cutSize = targetSize < data->Size()
+ (run.Length() << fVolume->BlockShift());
// if adding this block_run means overshooting the target stream size,
// we need to set data->size to targetSize.
// Direct block range
if (data->Size() <= data->MaxDirectRange()) {
// let's try to put them into the direct block range
int32 free = 0;
for (; free < NUM_DIRECT_BLOCKS; free++) {
if (data->direct[free].IsZero())
break;
}
if (free < NUM_DIRECT_BLOCKS) {
// can we merge the last allocated run with the new one?
int32 last = free - 1;
if (free > 0 && data->direct[last].MergeableWith(run)) {
data->direct[last].length = HOST_ENDIAN_TO_BFS_INT16(
data->direct[last].Length() + run.Length());
} else
data->direct[free] = run;
data->max_direct_range = HOST_ENDIAN_TO_BFS_INT64(
data->MaxDirectRange()
+ run.Length() * fVolume->BlockSize());
data->size = cutSize ? HOST_ENDIAN_TO_BFS_INT64(targetSize)
: data->max_direct_range;
return B_OK;
}
}
// Indirect block range
if (data->Size() <= data->MaxIndirectRange()
|| !data->MaxIndirectRange()) {
CachedBlock cached(fVolume);
block_run* runs = NULL;
uint32 free = 0;
off_t block;
// if there is no indirect block yet, create one
if (data->indirect.IsZero()) {
status = _AllocateBlockArray(transaction, data->indirect,
NUM_ARRAY_BLOCKS, true);
if (status != B_OK)
return status;
data->max_indirect_range = data->max_direct_range;
// insert the block_run in the first block
status = cached.SetTo(data->indirect);
if (status != B_OK)
return status;
runs = (block_run*)cached.Block();
} else {
uint32 numberOfRuns = fVolume->BlockSize() / sizeof(block_run);
block = fVolume->ToBlock(data->indirect);
// search first empty entry
int32 i = 0;
for (; i < data->indirect.Length(); i++) {
status = cached.SetTo(block + i);
if (status != B_OK)
return status;
runs = (block_run*)cached.Block();
for (free = 0; free < numberOfRuns; free++)
if (runs[free].IsZero())
break;
if (free < numberOfRuns)
break;
}
if (i == data->indirect.Length())
runs = NULL;
}
if (runs != NULL) {
// try to insert the run to the last one - note that this
// doesn't take block borders into account, so it could be
// further optimized
cached.MakeWritable(transaction);
int32 last = free - 1;
if (free > 0 && runs[last].MergeableWith(run)) {
runs[last].length = HOST_ENDIAN_TO_BFS_INT16(
runs[last].Length() + run.Length());
} else
runs[free] = run;
data->max_indirect_range = HOST_ENDIAN_TO_BFS_INT64(
data->MaxIndirectRange()
+ (run.Length() << fVolume->BlockShift()));
data->size = cutSize ? HOST_ENDIAN_TO_BFS_INT64(targetSize)
: data->max_indirect_range;
return B_OK;
}
}
// Double indirect block range
if (data->Size() <= data->MaxDoubleIndirectRange()
|| !data->max_double_indirect_range) {
// We make sure here that we have this minimum allocated, so if
// the allocation succeeds, we don't run into an endless loop.
uint16 doubleIndirectBlockLength;
if (!data->max_double_indirect_range)
doubleIndirectBlockLength = _DoubleIndirectBlockLength();
else
doubleIndirectBlockLength = data->double_indirect.Length();
if ((run.Length() % doubleIndirectBlockLength) != 0) {
// The number of allocated blocks isn't a multiple of
// 'doubleIndirectBlockLength', so we return and let the caller
// change this. This can happen the first time the stream grows
// into the double indirect range.
if (rest) {
*rest = run.Length() % doubleIndirectBlockLength;
return B_OK;
}
// the caller didn't expect rest
return B_BAD_VALUE;
}
// if there is no double indirect block yet, create one
if (data->double_indirect.IsZero()) {
status = _AllocateBlockArray(transaction,
data->double_indirect, _DoubleIndirectBlockLength());
if (status != B_OK)
return status;
data->max_double_indirect_range = data->max_indirect_range;
}
// calculate the index where to insert the new blocks
int32 runsPerBlock;
int32 directSize;
int32 indirectSize;
get_double_indirect_sizes(data->double_indirect.Length(),
fVolume->BlockSize(), runsPerBlock, directSize, indirectSize);
if (directSize <= 0 || indirectSize <= 0)
return B_BAD_DATA;
off_t start = data->MaxDoubleIndirectRange()
- data->MaxIndirectRange();
int32 indirectIndex = start / indirectSize;
int32 index = (start % indirectSize) / directSize;
int32 runsPerArray = runsPerBlock * doubleIndirectBlockLength;
// distribute the blocks to the array and allocate
// new array blocks when needed
CachedBlock cached(fVolume);
CachedBlock cachedDirect(fVolume);
block_run* array = NULL;
uint32 runLength = run.Length();
while (run.length != 0) {
// get the indirect array block
if (array == NULL) {
uint32 block = indirectIndex / runsPerBlock;
if (block >= doubleIndirectBlockLength)
return EFBIG;
status = cached.SetTo(fVolume->ToBlock(
data->double_indirect) + block);
if (status != B_OK)
return status;
array = (block_run*)cached.Block();
}
do {
// do we need a new array block?
if (array[indirectIndex % runsPerBlock].IsZero()) {
cached.MakeWritable(transaction);
status = _AllocateBlockArray(transaction,
array[indirectIndex % runsPerBlock],
data->double_indirect.Length());
if (status != B_OK)
return status;
}
status = cachedDirect.SetToWritable(transaction,
fVolume->ToBlock(array[indirectIndex
% runsPerBlock]) + index / runsPerBlock);
if (status != B_OK)
return status;
block_run* runs = (block_run*)cachedDirect.Block();
do {
// insert the block_run into the array
runs[index % runsPerBlock] = run;
runs[index % runsPerBlock].length
= HOST_ENDIAN_TO_BFS_INT16(doubleIndirectBlockLength);
// alter the remaining block_run
run.start = HOST_ENDIAN_TO_BFS_INT16(run.Start()
+ doubleIndirectBlockLength);
run.length = HOST_ENDIAN_TO_BFS_INT16(run.Length()
- doubleIndirectBlockLength);
} while ((++index % runsPerBlock) != 0 && run.length);
} while ((index % runsPerArray) != 0 && run.length);
if (index == runsPerArray)
index = 0;
if (++indirectIndex % runsPerBlock == 0) {
array = NULL;
index = 0;
}
}
data->max_double_indirect_range = HOST_ENDIAN_TO_BFS_INT64(
data->MaxDoubleIndirectRange()
+ (runLength << fVolume->BlockShift()));
data->size = cutSize ? HOST_ENDIAN_TO_BFS_INT64(targetSize)
: data->max_double_indirect_range;
return B_OK;
}
RETURN_ERROR(EFBIG);
}
/*! Grows the stream to \a size, and fills the direct/indirect/double indirect
ranges with the runs.
This method will also determine the size of the preallocation, if any.
@@ -1803,6 +2051,12 @@ Inode::_GrowStream(Transaction& transaction, off_t size)
// the requested blocks do not need to be returned with a
// single allocation, so we need to iterate until we have
// enough blocks allocated
// If data has a double_indirect block, we're adding block_run:s to
// the double indirect range.
if (!data->double_indirect.IsZero())
minimum = data->double_indirect.Length();
if (minimum > 1) {
// make sure that "blocks" is a multiple of minimum
blocksRequested = round_up(blocksRequested, minimum);
@@ -1821,239 +2075,34 @@ Inode::_GrowStream(Transaction& transaction, off_t size)
// don't preallocate if the first allocation was already too small
blocksRequested = blocksNeeded;
// Direct block range
int32 rest;
status = _AddBlockRun(transaction, data, run, size, &rest);
if (status != B_OK)
return status;
if (data->Size() <= data->MaxDirectRange()) {
// let's try to put them into the direct block range
int32 free = 0;
for (; free < NUM_DIRECT_BLOCKS; free++) {
if (data->direct[free].IsZero())
break;
}
if (rest != 0) {
// We've entered the double indirect range, and the number of
// allocated blocks isn't a multiple of 'doubleIndirectBlockLength'
if (free < NUM_DIRECT_BLOCKS) {
// can we merge the last allocated run with the new one?
int32 last = free - 1;
if (free > 0 && data->direct[last].MergeableWith(run)) {
data->direct[last].length = HOST_ENDIAN_TO_BFS_INT16(
data->direct[last].Length() + run.Length());
} else
data->direct[free] = run;
minimum = _DoubleIndirectBlockLength();
data->max_direct_range = HOST_ENDIAN_TO_BFS_INT64(
data->MaxDirectRange()
+ run.Length() * fVolume->BlockSize());
data->size = HOST_ENDIAN_TO_BFS_INT64(blocksNeeded > 0
? data->max_direct_range : size);
// Free the remaining blocks that don't fit into this multiple.
run.length = HOST_ENDIAN_TO_BFS_INT16(run.Length() - rest);
status = fVolume->Free(transaction,
block_run::Run(run.AllocationGroup(),
run.Start() + run.Length(), rest));
if (status != B_OK)
return status;
blocksNeeded += rest;
blocksRequested = round_up(blocksNeeded, minimum);
// Are there any blocks left in the run? If not, allocate
// a new one
if (run.length == 0)
continue;
}
}
// Indirect block range
if (data->Size() <= data->MaxIndirectRange()
|| !data->MaxIndirectRange()) {
CachedBlock cached(fVolume);
block_run* runs = NULL;
uint32 free = 0;
off_t block;
// if there is no indirect block yet, create one
if (data->indirect.IsZero()) {
status = _AllocateBlockArray(transaction, data->indirect,
NUM_ARRAY_BLOCKS, true);
if (status != B_OK)
return status;
data->max_indirect_range = HOST_ENDIAN_TO_BFS_INT64(
data->MaxDirectRange());
// insert the block_run in the first block
status = cached.SetTo(data->indirect);
if (status != B_OK)
return status;
runs = (block_run*)cached.Block();
} else {
uint32 numberOfRuns = fVolume->BlockSize() / sizeof(block_run);
block = fVolume->ToBlock(data->indirect);
// search first empty entry
int32 i = 0;
for (; i < data->indirect.Length(); i++) {
status = cached.SetTo(block + i);
if (status != B_OK)
return status;
runs = (block_run*)cached.Block();
for (free = 0; free < numberOfRuns; free++)
if (runs[free].IsZero())
break;
if (free < numberOfRuns)
break;
}
if (i == data->indirect.Length())
runs = NULL;
}
if (runs != NULL) {
// try to insert the run to the last one - note that this
// doesn't take block borders into account, so it could be
// further optimized
cached.MakeWritable(transaction);
int32 last = free - 1;
if (free > 0 && runs[last].MergeableWith(run)) {
runs[last].length = HOST_ENDIAN_TO_BFS_INT16(
runs[last].Length() + run.Length());
} else
runs[free] = run;
data->max_indirect_range = HOST_ENDIAN_TO_BFS_INT64(
data->MaxIndirectRange()
+ ((uint32)run.Length() << fVolume->BlockShift()));
data->size = HOST_ENDIAN_TO_BFS_INT64(blocksNeeded > 0
? data->MaxIndirectRange() : size);
continue;
}
}
// Double indirect block range
if (data->Size() <= data->MaxDoubleIndirectRange()
|| !data->max_double_indirect_range) {
// We make sure here that we have this minimum allocated, so if
// the allocation succeeds, we don't run into an endless loop.
if (!data->max_double_indirect_range)
minimum = _DoubleIndirectBlockLength();
else
minimum = data->double_indirect.Length();
if ((run.Length() % minimum) != 0) {
// The number of allocated blocks isn't a multiple of 'minimum',
// so we have to change this. This can happen the first time the
// stream grows into the double indirect range.
// First, free the remaining blocks that don't fit into this
// multiple.
int32 rest = run.Length() % minimum;
run.length = HOST_ENDIAN_TO_BFS_INT16(run.Length() - rest);
status = fVolume->Free(transaction,
block_run::Run(run.AllocationGroup(),
run.Start() + run.Length(), rest));
if (status != B_OK)
return status;
blocksNeeded += rest;
blocksRequested = round_up(blocksNeeded, minimum);
// Are there any blocks left in the run? If not, allocate
// a new one
if (run.length == 0)
continue;
}
// if there is no double indirect block yet, create one
if (data->double_indirect.IsZero()) {
status = _AllocateBlockArray(transaction,
data->double_indirect, _DoubleIndirectBlockLength());
if (status != B_OK)
return status;
data->max_double_indirect_range = data->max_indirect_range;
}
// calculate the index where to insert the new blocks
int32 runsPerBlock;
int32 directSize;
int32 indirectSize;
get_double_indirect_sizes(data->double_indirect.Length(),
fVolume->BlockSize(), runsPerBlock, directSize, indirectSize);
if (directSize <= 0 || indirectSize <= 0)
return B_BAD_DATA;
off_t start = data->MaxDoubleIndirectRange()
- data->MaxIndirectRange();
int32 indirectIndex = start / indirectSize;
int32 index = (start % indirectSize) / directSize;
int32 runsPerArray = runsPerBlock * minimum;
// distribute the blocks to the array and allocate
// new array blocks when needed
CachedBlock cached(fVolume);
CachedBlock cachedDirect(fVolume);
block_run* array = NULL;
uint32 runLength = run.Length();
while (run.length != 0) {
// get the indirect array block
if (array == NULL) {
uint32 block = indirectIndex / runsPerBlock;
if (block >= minimum)
return EFBIG;
status = cached.SetTo(fVolume->ToBlock(
data->double_indirect) + block);
if (status != B_OK)
return status;
array = (block_run*)cached.Block();
}
do {
// do we need a new array block?
if (array[indirectIndex % runsPerBlock].IsZero()) {
cached.MakeWritable(transaction);
status = _AllocateBlockArray(transaction,
array[indirectIndex % runsPerBlock],
data->double_indirect.Length());
if (status != B_OK)
return status;
}
status = cachedDirect.SetToWritable(transaction,
fVolume->ToBlock(array[indirectIndex
% runsPerBlock]) + index / runsPerBlock);
if (status != B_OK)
return status;
block_run* runs = (block_run*)cachedDirect.Block();
do {
// insert the block_run into the array
runs[index % runsPerBlock] = run;
runs[index % runsPerBlock].length
= HOST_ENDIAN_TO_BFS_INT16(minimum);
// alter the remaining block_run
run.start = HOST_ENDIAN_TO_BFS_INT16(run.Start()
+ minimum);
run.length = HOST_ENDIAN_TO_BFS_INT16(run.Length()
- minimum);
} while ((++index % runsPerBlock) != 0 && run.length);
} while ((index % runsPerArray) != 0 && run.length);
if (index == runsPerArray)
index = 0;
if (++indirectIndex % runsPerBlock == 0) {
array = NULL;
index = 0;
}
}
data->max_double_indirect_range = HOST_ENDIAN_TO_BFS_INT64(
data->MaxDoubleIndirectRange()
+ (runLength << fVolume->BlockShift()));
data->size = blocksNeeded > 0 ? HOST_ENDIAN_TO_BFS_INT64(
data->max_double_indirect_range) : size;
continue;
}
RETURN_ERROR(EFBIG);
}
// update the size of the data stream
data->size = HOST_ENDIAN_TO_BFS_INT64(size);
@@ -256,6 +256,10 @@ private:
off_t size);
status_t _ShrinkStream(Transaction& transaction,
off_t size);
status_t _AddBlockRun(Transaction& transaction,
data_stream* data, block_run run,
off_t targetSize, int32* rest = NULL,
off_t beginBlock = 0, off_t endBlock = 0);
private:
rw_lock fLock;