⛏️ index : haiku.git

/*
 * Copyright 2001-2010, Haiku Inc. All rights reserved.
 * This file may be used under the terms of the MIT License.
 *
 * Authors:
 *		Janito V. Ferreira Filho
 */


#include "DataStream.h"

#include "CachedBlock.h"
#include "Volume.h"


//#define TRACE_EXT2
#ifdef TRACE_EXT2
#	define TRACE(x...) dprintf("\33[34mext2:\33[0m " x)
#else
#	define TRACE(x...) ;
#endif
#define ERROR(x...)	dprintf("\33[34mext2:\33[0m " x)


DataStream::DataStream(Volume* volume, ext2_data_stream* stream,
	off_t size)
	:
	kBlockSize(volume->BlockSize()),
	kIndirectsPerBlock(kBlockSize / sizeof(uint32)),
	kIndirectsPerBlock2(kIndirectsPerBlock * kIndirectsPerBlock),
	kIndirectsPerBlock3(kIndirectsPerBlock2 * kIndirectsPerBlock),
	kMaxDirect(EXT2_DIRECT_BLOCKS),
	kMaxIndirect(kMaxDirect + kIndirectsPerBlock),
	kMaxDoubleIndirect(kMaxIndirect + kIndirectsPerBlock2),
	fVolume(volume),
	fStream(stream),
	fFirstBlock(volume->FirstDataBlock()),
	fAllocated(0),
	fAllocatedPos(fFirstBlock),
	fWaiting(0),
	fFreeStart(0),
	fFreeCount(0),
	fRemovedBlocks(0),
	fSize(size)
{
	fNumBlocks = size == 0 ? 0 : ((size - 1) >> fVolume->BlockShift()) + 1;
}


DataStream::~DataStream()
{
}


status_t
DataStream::FindBlock(off_t offset, fsblock_t& block, uint32 *_count)
{
	uint32 index = offset >> fVolume->BlockShift();

	if (offset >= fSize) {
		TRACE("FindBlock: offset larger than inode size\n");
		return B_ENTRY_NOT_FOUND;
	}

	// TODO: we could return the size of the sparse range, as this might be more
	// than just a block

	if (index < EXT2_DIRECT_BLOCKS) {
		// direct blocks
		block = B_LENDIAN_TO_HOST_INT32(fStream->direct[index]);
		ASSERT(block != 0);
		if (_count) {
			*_count = 1;
			uint32 nextBlock = block;
			while (++index < EXT2_DIRECT_BLOCKS
				&& fStream->direct[index] == ++nextBlock)
				(*_count)++;
		}
	} else if ((index -= EXT2_DIRECT_BLOCKS) < kIndirectsPerBlock) {
		// indirect blocks
		CachedBlock cached(fVolume);
		uint32* indirectBlocks = (uint32*)cached.SetTo(B_LENDIAN_TO_HOST_INT32(
			fStream->indirect));
		if (indirectBlocks == NULL)
			return B_IO_ERROR;

		block = B_LENDIAN_TO_HOST_INT32(indirectBlocks[index]);
		ASSERT(block != 0);
		if (_count) {
			*_count = 1;
			uint32 nextBlock = block;
			while (++index < kIndirectsPerBlock
				&& indirectBlocks[index] == ++nextBlock)
				(*_count)++;
		}
	} else if ((index -= kIndirectsPerBlock) < kIndirectsPerBlock2) {
		// double indirect blocks
		CachedBlock cached(fVolume);
		uint32* indirectBlocks = (uint32*)cached.SetTo(B_LENDIAN_TO_HOST_INT32(
			fStream->double_indirect));
		if (indirectBlocks == NULL)
			return B_IO_ERROR;

		uint32 indirectIndex = B_LENDIAN_TO_HOST_INT32(indirectBlocks[index 
			/ kIndirectsPerBlock]);
		if (indirectIndex == 0) {
			// a sparse indirect block
			block = 0;
		} else {
			indirectBlocks = (uint32*)cached.SetTo(indirectIndex);
			if (indirectBlocks == NULL)
				return B_IO_ERROR;

			block = B_LENDIAN_TO_HOST_INT32(
				indirectBlocks[index & (kIndirectsPerBlock - 1)]);
			if (_count) {
				*_count = 1;
				uint32 nextBlock = block;
				while (((++index & (kIndirectsPerBlock - 1)) != 0)
					&& indirectBlocks[index & (kIndirectsPerBlock - 1)]
						== ++nextBlock)
					(*_count)++;
			}
		}
		ASSERT(block != 0);
	} else if ((index -= kIndirectsPerBlock2) < kIndirectsPerBlock3) {
		// triple indirect blocks
		CachedBlock cached(fVolume);
		uint32* indirectBlocks = (uint32*)cached.SetTo(B_LENDIAN_TO_HOST_INT32(
			fStream->triple_indirect));
		if (indirectBlocks == NULL)
			return B_IO_ERROR;

		uint32 indirectIndex = B_LENDIAN_TO_HOST_INT32(indirectBlocks[index
			/ kIndirectsPerBlock2]);
		if (indirectIndex == 0) {
			// a sparse indirect block
			block = 0;
		} else {
			indirectBlocks = (uint32*)cached.SetTo(indirectIndex);
			if (indirectBlocks == NULL)
				return B_IO_ERROR;

			indirectIndex = B_LENDIAN_TO_HOST_INT32(
				indirectBlocks[(index / kIndirectsPerBlock) & (kIndirectsPerBlock - 1)]);
			if (indirectIndex == 0) {
				// a sparse indirect block
				block = 0;
			} else {
				indirectBlocks = (uint32*)cached.SetTo(indirectIndex);
				if (indirectBlocks == NULL)
					return B_IO_ERROR;

				block = B_LENDIAN_TO_HOST_INT32(
					indirectBlocks[index & (kIndirectsPerBlock - 1)]);
				if (_count) {
					*_count = 1;
					uint32 nextBlock = block;
					while (((++index & (kIndirectsPerBlock - 1)) != 0)
						&& indirectBlocks[index & (kIndirectsPerBlock - 1)]
							== ++nextBlock)
						(*_count)++;
				}
			}
		}
		ASSERT(block != 0);
	} else {
		// Outside of the possible data stream
		ERROR("ext2: block outside datastream!\n");
		return B_ERROR;
	}

	TRACE("FindBlock(offset %" B_PRIdOFF "): %" B_PRIu64" %" B_PRIu32 "\n", offset,
		block, _count != NULL ? *_count : 1);
	return B_OK;
}


status_t
DataStream::Enlarge(Transaction& transaction, off_t& numBlocks)
{
	TRACE("DataStream::Enlarge(): current size: %" B_PRIdOFF ", target size: %"
		B_PRIdOFF "\n", fNumBlocks, numBlocks);
	
	off_t targetBlocks = numBlocks;
	fWaiting = _BlocksNeeded(numBlocks);
	numBlocks = fWaiting;

	status_t status;

	if (fNumBlocks <= kMaxDirect) {
		status = _AddForDirectBlocks(transaction, targetBlocks);

		if (status != B_OK) {
			ERROR("DataStream::Enlarge(): _AddForDirectBlocks() failed\n");
			return status;
		}

		TRACE("DataStream::Enlarge(): current size: %" B_PRIdOFF
			", target size: %" B_PRIdOFF "\n", fNumBlocks, targetBlocks);
	
		if (fNumBlocks == targetBlocks)
			return B_OK;
	}

	TRACE("DataStream::Enlarge(): indirect current size: %" B_PRIdOFF
		", target size: %" B_PRIdOFF "\n", fNumBlocks, targetBlocks);

	if (fNumBlocks <= kMaxIndirect) {
		status = _AddForIndirectBlock(transaction, targetBlocks);

		if (status != B_OK) {
			ERROR("DataStream::Enlarge(): _AddForIndirectBlock() failed\n");
			return status;
		}

		TRACE("DataStream::Enlarge(): current size: %" B_PRIdOFF
			", target size: %" B_PRIdOFF "\n", fNumBlocks, targetBlocks);
	
		if (fNumBlocks == targetBlocks)
			return B_OK;
	}

	TRACE("DataStream::Enlarge(): indirect2 current size: %" B_PRIdOFF
		", target size: %" B_PRIdOFF "\n", fNumBlocks, targetBlocks);

	if (fNumBlocks <= kMaxDoubleIndirect) {
		status = _AddForDoubleIndirectBlock(transaction, targetBlocks);

		if (status != B_OK) {
			ERROR("DataStream::Enlarge(): _AddForDoubleIndirectBlock() failed\n");
			return status;
		}

		TRACE("DataStream::Enlarge(): current size: %" B_PRIdOFF
			", target size: %" B_PRIdOFF "\n", fNumBlocks, targetBlocks);
	
		if (fNumBlocks == targetBlocks)
			return B_OK;
	}

	TRACE("DataStream::Enlarge(): indirect3 current size: %" B_PRIdOFF
		", target size: %" B_PRIdOFF "\n", fNumBlocks, targetBlocks);

	TRACE("DataStream::Enlarge(): allocated: %" B_PRIu32 ", waiting: %"
		B_PRIu32 "\n", fAllocated, fWaiting);

	return _AddForTripleIndirectBlock(transaction, targetBlocks);
}


status_t
DataStream::Shrink(Transaction& transaction, off_t& numBlocks)
{
	TRACE("DataStream::Shrink(): current size: %" B_PRIdOFF ", target size: %"
		B_PRIdOFF "\n", fNumBlocks, numBlocks);

	fFreeStart = 0;
	fFreeCount = 0;
	fRemovedBlocks = 0;

	off_t oldNumBlocks = fNumBlocks;
	off_t blocksToRemove = fNumBlocks - numBlocks;

	status_t status;

	if (numBlocks < kMaxDirect) {
		status = _RemoveFromDirectBlocks(transaction, numBlocks);

		if (status != B_OK) {
			ERROR("DataStream::Shrink(): _RemoveFromDirectBlocks() failed\n");
			return status;
		}

		if (fRemovedBlocks == blocksToRemove) {
			fNumBlocks -= fRemovedBlocks;
			numBlocks = _BlocksNeeded(oldNumBlocks);

			return _PerformFree(transaction);
		}
	}

	if (numBlocks < kMaxIndirect) {
		status = _RemoveFromIndirectBlock(transaction, numBlocks);

		if (status != B_OK) {
			ERROR("DataStream::Shrink(): _RemoveFromIndirectBlock() failed\n");
			return status;
		}

		if (fRemovedBlocks == blocksToRemove) {
			fNumBlocks -= fRemovedBlocks;
			numBlocks = _BlocksNeeded(oldNumBlocks);

			return _PerformFree(transaction);
		}
	}

	if (numBlocks < kMaxDoubleIndirect) {
		status = _RemoveFromDoubleIndirectBlock(transaction, numBlocks);

		if (status != B_OK) {
			ERROR("DataStream::Shrink(): _RemoveFromDoubleIndirectBlock() failed\n");
			return status;
		}

		if (fRemovedBlocks == blocksToRemove) {
			fNumBlocks -= fRemovedBlocks;
			numBlocks = _BlocksNeeded(oldNumBlocks);

			return _PerformFree(transaction);
		}
	}

	status = _RemoveFromTripleIndirectBlock(transaction, numBlocks);

	if (status != B_OK) {
		ERROR("DataStream::Shrink(): _RemoveFromTripleIndirectBlock() failed\n");
		return status;
	}

	fNumBlocks -= fRemovedBlocks;
	numBlocks = _BlocksNeeded(oldNumBlocks);

	return _PerformFree(transaction);
}


uint32
DataStream::_BlocksNeeded(off_t numBlocks)
{
	TRACE("DataStream::BlocksNeeded(): num blocks %" B_PRIdOFF "\n", numBlocks);
	off_t blocksNeeded = 0;

	if (numBlocks > fNumBlocks) {
		blocksNeeded += numBlocks - fNumBlocks;

		if (numBlocks > kMaxDirect) {
			if (fNumBlocks <= kMaxDirect)
				blocksNeeded += 1;

			if (numBlocks > kMaxIndirect) {
				if (fNumBlocks <= kMaxIndirect) {
					blocksNeeded += 2 + (numBlocks - kMaxIndirect - 1)
						/ kIndirectsPerBlock;
				} else {
					blocksNeeded += (numBlocks - kMaxIndirect - 1)
						/ kIndirectsPerBlock - (fNumBlocks
							- kMaxIndirect - 1) / kIndirectsPerBlock;
				}

				if (numBlocks > kMaxDoubleIndirect) {
					if (fNumBlocks <= kMaxDoubleIndirect) {
						blocksNeeded += 2 + (numBlocks - kMaxDoubleIndirect - 1)
							/ kIndirectsPerBlock2;
					} else {
						blocksNeeded += (numBlocks - kMaxDoubleIndirect - 1)
							/ kIndirectsPerBlock - (fNumBlocks 
								- kMaxDoubleIndirect - 1) / kIndirectsPerBlock;
					}
				}
			}
		}
	}

	TRACE("DataStream::BlocksNeeded(): %" B_PRIdOFF "\n", blocksNeeded);
	return blocksNeeded;
}


status_t
DataStream::_GetBlock(Transaction& transaction, uint32& blockNum)
{
	TRACE("DataStream::_GetBlock(): allocated: %" B_PRIu32 ", pos: %" B_PRIu64
		", waiting: %" B_PRIu32 "\n", fAllocated, fAllocatedPos, fWaiting);

	if (fAllocated == 0) {
		uint32 blockGroup = (fAllocatedPos - fFirstBlock)
			/ fVolume->BlocksPerGroup();
		
		status_t status = fVolume->AllocateBlocks(transaction, 1, fWaiting,
			blockGroup, fAllocatedPos, fAllocated);
		if (status != B_OK) {
			ERROR("DataStream::_GetBlock(): AllocateBlocks() failed()\n");
			return status;
		}
		if (fAllocatedPos > UINT_MAX)
			return B_FILE_TOO_LARGE;

		fWaiting -= fAllocated;

		TRACE("DataStream::_GetBlock(): newAllocated: %" B_PRIu32 ", newpos: %"
			B_PRIu64 ", newwaiting: %" B_PRIu32 "\n", fAllocated,
			fAllocatedPos, fWaiting);
	}

	fAllocated--;
	blockNum = (uint32)fAllocatedPos++;

	return B_OK;
}


status_t
DataStream::_PrepareBlock(Transaction& transaction, uint32* pos,
	uint32& blockNum, bool& clear)
{
	blockNum = B_LENDIAN_TO_HOST_INT32(*pos);
	clear = false;

	if (blockNum == 0) {
		status_t status = _GetBlock(transaction, blockNum);
		if (status != B_OK) {
			ERROR("DataStream::_PrepareBlock() _GetBlock() failed blockNum %"
				B_PRIu32 "\n", blockNum);
			return status;
		}

		*pos = B_HOST_TO_LENDIAN_INT32(blockNum);
		clear = true;
	}

	return B_OK;
}


status_t
DataStream::_AddBlocks(Transaction& transaction, uint32* block, off_t _count)
{
	off_t count = _count;
	TRACE("DataStream::_AddBlocks(): count: %" B_PRIdOFF "\n", count);

	while (count > 0) {
		uint32 blockNum;
		status_t status = _GetBlock(transaction, blockNum);
		if (status != B_OK)
			return status;

		*(block++) = B_HOST_TO_LENDIAN_INT32(blockNum);
		--count;
	}

	fNumBlocks += _count;

	return B_OK;
}


status_t
DataStream::_AddBlocks(Transaction& transaction, uint32* block, off_t start,
	off_t end, int recursion)
{
	TRACE("DataStream::_AddBlocks(): start: %" B_PRIdOFF ", end %" B_PRIdOFF
		", recursion: %d\n", start, end, recursion);

	bool clear;
	uint32 blockNum;
	status_t status = _PrepareBlock(transaction, block, blockNum, clear);
	if (status != B_OK)
		return status;

	CachedBlock cached(fVolume);	
	uint32* childBlock = (uint32*)cached.SetToWritable(transaction, blockNum,
		clear);
	if (childBlock == NULL)
		return B_IO_ERROR;

	if (recursion == 0)
		return _AddBlocks(transaction, &childBlock[start], end - start);

	uint32 elementWidth;
	if (recursion == 1)
		elementWidth = kIndirectsPerBlock;
	else if (recursion == 2)
		elementWidth = kIndirectsPerBlock2;
	else {
		panic("Undefined recursion level\n");
		elementWidth = 0;
	}

	uint32 elementPos = start / elementWidth;
	uint32 endPos = end / elementWidth;

	TRACE("DataStream::_AddBlocks(): element pos: %" B_PRIu32 ", end pos: %"
		B_PRIu32 "\n", elementPos, endPos);

	recursion--;

	if (elementPos == endPos) {
		return _AddBlocks(transaction, &childBlock[elementPos],
			start % elementWidth, end % elementWidth, recursion);
	}
	
	if (start % elementWidth != 0) {
		status = _AddBlocks(transaction, &childBlock[elementPos],
			start % elementWidth, elementWidth, recursion);
		if (status != B_OK) {
			ERROR("DataStream::_AddBlocks() _AddBlocks() start failed\n");
			return status;
		}

		elementPos++;
	}

	while (elementPos < endPos) {
		status = _AddBlocks(transaction, &childBlock[elementPos], 0,
			elementWidth, recursion);
		if (status != B_OK) {
			ERROR("DataStream::_AddBlocks() _AddBlocks() mid failed\n");
			return status;
		}

		elementPos++;
	}

	if (end % elementWidth != 0) {
		status = _AddBlocks(transaction, &childBlock[elementPos], 0,
			end % elementWidth, recursion);
		if (status != B_OK) {
			ERROR("DataStream::_AddBlocks() _AddBlocks() end failed\n");
			return status;
		}
	}
		
	return B_OK;
}


status_t
DataStream::_AddForDirectBlocks(Transaction& transaction, uint32 numBlocks)
{
	TRACE("DataStream::_AddForDirectBlocks(): current size: %" B_PRIdOFF
		", target size: %" B_PRIu32 "\n", fNumBlocks, numBlocks);
	uint32* direct = &fStream->direct[fNumBlocks];
	uint32 end = numBlocks > kMaxDirect ? kMaxDirect : numBlocks;

	return _AddBlocks(transaction, direct, end - fNumBlocks);
}


status_t
DataStream::_AddForIndirectBlock(Transaction& transaction, uint32 numBlocks)
{
	TRACE("DataStream::_AddForIndirectBlocks(): current size: %" B_PRIdOFF
		", target size: %" B_PRIu32 "\n", fNumBlocks, numBlocks);
	uint32 *indirect = &fStream->indirect;
	uint32 start = fNumBlocks - kMaxDirect;
	uint32 end = numBlocks - kMaxDirect;

	if (end > kIndirectsPerBlock)
		end = kIndirectsPerBlock;

	return _AddBlocks(transaction, indirect, start, end, 0);
}


status_t
DataStream::_AddForDoubleIndirectBlock(Transaction& transaction,
	uint32 numBlocks)
{
	TRACE("DataStream::_AddForDoubleIndirectBlock(): current size: %" B_PRIdOFF
		", target size: %" B_PRIu32 "\n", fNumBlocks, numBlocks);
	uint32 *doubleIndirect = &fStream->double_indirect;
	uint32 start = fNumBlocks - kMaxIndirect;
	uint32 end = numBlocks - kMaxIndirect;

	if (end > kIndirectsPerBlock2)
		end = kIndirectsPerBlock2;

	return _AddBlocks(transaction, doubleIndirect, start, end, 1);
}


status_t
DataStream::_AddForTripleIndirectBlock(Transaction& transaction,
	uint32 numBlocks)
{
	TRACE("DataStream::_AddForTripleIndirectBlock(): current size: %" B_PRIdOFF
		", target size: %" B_PRIu32 "\n", fNumBlocks, numBlocks);
	uint32 *tripleIndirect = &fStream->triple_indirect;
	uint32 start = fNumBlocks - kMaxDoubleIndirect;
	uint32 end = numBlocks - kMaxDoubleIndirect;

	return _AddBlocks(transaction, tripleIndirect, start, end, 2);
}


status_t
DataStream::_PerformFree(Transaction& transaction)
{
	TRACE("DataStream::_PerformFree(): start: %" B_PRIu32 ", count: %" B_PRIu32
		"\n", fFreeStart, fFreeCount);
	status_t status;

	if (fFreeCount == 0)
		status = B_OK;
	else
		status = fVolume->FreeBlocks(transaction, fFreeStart, fFreeCount);

	fFreeStart = 0;
	fFreeCount = 0;

	return status;
}


status_t
DataStream::_MarkBlockForRemoval(Transaction& transaction, uint32* block)
{
	
	TRACE("DataStream::_MarkBlockForRemoval(*(%p) = %" B_PRIu32
		"): free start: %" B_PRIu32 ", free count: %" B_PRIu32 "\n", block,
		B_LENDIAN_TO_HOST_INT32(*block), fFreeStart, fFreeCount);
	uint32 blockNum = B_LENDIAN_TO_HOST_INT32(*block);
	*block = 0;

	if (blockNum != fFreeStart + fFreeCount) {
		if (fFreeCount != 0) {
			status_t status = fVolume->FreeBlocks(transaction, fFreeStart,
				fFreeCount);
			if (status != B_OK)
				return status;
		}

		fFreeStart = blockNum;
		fFreeCount = 0;
	}

	fFreeCount++;

	return B_OK;
}


status_t
DataStream::_FreeBlocks(Transaction& transaction, uint32* block, uint32 _count)
{
	uint32 count = _count;
	TRACE("DataStream::_FreeBlocks(%p, %" B_PRIu32 ")\n", block, count);

	while (count > 0) {
		status_t status = _MarkBlockForRemoval(transaction, block);
		if (status != B_OK)
			return status;

		block++;
		count--;
	}

	fRemovedBlocks += _count;

	return B_OK;
}


status_t
DataStream::_FreeBlocks(Transaction& transaction, uint32* block, off_t start,
	off_t end, bool freeParent, int recursion)
{
	// TODO: Designed specifically for shrinking. Perhaps make it more general?
	TRACE("DataStream::_FreeBlocks(%p, %" B_PRIdOFF ", %" B_PRIdOFF
		", %c, %d)\n", block, start, end, freeParent ? 't' : 'f', recursion);

	uint32 blockNum = B_LENDIAN_TO_HOST_INT32(*block);

	if (freeParent) {
		status_t status = _MarkBlockForRemoval(transaction, block);
		if (status != B_OK)
			return status;
	}

	CachedBlock cached(fVolume);
	uint32* childBlock = (uint32*)cached.SetToWritable(transaction, blockNum);
	if (childBlock == NULL)
		return B_IO_ERROR;

	if (recursion == 0)
		return _FreeBlocks(transaction, &childBlock[start], end - start);

	uint32 elementWidth;
	if (recursion == 1)
		elementWidth = kIndirectsPerBlock;
	else if (recursion == 2)
		elementWidth = kIndirectsPerBlock2;
	else {
		panic("Undefinied recursion level\n");
		elementWidth = 0;
	}

	uint32 elementPos = start / elementWidth;
	uint32 endPos = end / elementWidth;

	recursion--;

	if (elementPos == endPos) {
		bool free = freeParent || start % elementWidth == 0;
		return _FreeBlocks(transaction, &childBlock[elementPos],
			start % elementWidth, end % elementWidth, free, recursion);
	}

	status_t status = B_OK;

	if (start % elementWidth != 0) {
		status = _FreeBlocks(transaction, &childBlock[elementPos],
			start % elementWidth, elementWidth, false, recursion);
		if (status != B_OK)
			return status;

		elementPos++;
	}

	while (elementPos < endPos) {
		status = _FreeBlocks(transaction, &childBlock[elementPos], 0,
			elementWidth, true, recursion);
		if (status != B_OK)
			return status;

		elementPos++;
	}

	if (end % elementWidth != 0) {
		status = _FreeBlocks(transaction, &childBlock[elementPos], 0,
			end % elementWidth, true, recursion);
	}

	return status;
}


status_t
DataStream::_RemoveFromDirectBlocks(Transaction& transaction, uint32 numBlocks)
{
	TRACE("DataStream::_RemoveFromDirectBlocks(): current size: %" B_PRIdOFF
		", target size: %" B_PRIu32 "\n", fNumBlocks, numBlocks);
	uint32* direct = &fStream->direct[numBlocks];
	off_t end = fNumBlocks > kMaxDirect ? kMaxDirect : fNumBlocks;

	return _FreeBlocks(transaction, direct, end - numBlocks);
}


status_t
DataStream::_RemoveFromIndirectBlock(Transaction& transaction, uint32 numBlocks)
{
	TRACE("DataStream::_RemoveFromIndirectBlock(): current size: %" B_PRIdOFF
		", target size: %" B_PRIu32 "\n", fNumBlocks, numBlocks);
	uint32* indirect = &fStream->indirect;
	off_t start = numBlocks <= kMaxDirect ? 0 : numBlocks - kMaxDirect;
	off_t end = fNumBlocks - kMaxDirect;

	if (end > kIndirectsPerBlock)
		end = kIndirectsPerBlock;

	bool freeAll = start == 0;

	return _FreeBlocks(transaction, indirect, start, end, freeAll, 0);
}


status_t
DataStream::_RemoveFromDoubleIndirectBlock(Transaction& transaction,
	uint32 numBlocks)
{
	TRACE("DataStream::_RemoveFromDoubleIndirectBlock(): current size: %" B_PRIdOFF
		", target size: %" B_PRIu32 "\n", fNumBlocks, numBlocks);
	uint32* doubleIndirect = &fStream->double_indirect;
	off_t start = numBlocks <= kMaxIndirect ? 0 : numBlocks - kMaxIndirect;
	off_t end = fNumBlocks - kMaxIndirect;

	if (end > kIndirectsPerBlock2)
		end = kIndirectsPerBlock2;

	bool freeAll = start == 0;

	return _FreeBlocks(transaction, doubleIndirect, start, end, freeAll, 1);
}


status_t
DataStream::_RemoveFromTripleIndirectBlock(Transaction& transaction,
	uint32 numBlocks)
{
	TRACE("DataStream::_RemoveFromTripleIndirectBlock(): current size: %" B_PRIdOFF
		", target size: %" B_PRIu32 "\n", fNumBlocks, numBlocks);
	uint32* tripleIndirect = &fStream->triple_indirect;
	off_t start = numBlocks <= kMaxDoubleIndirect ? 0
		: numBlocks - kMaxDoubleIndirect;
	off_t end = fNumBlocks - kMaxDoubleIndirect;

	bool freeAll = start == 0;

	return _FreeBlocks(transaction, tripleIndirect, start, end, freeAll, 2);
}