1/*
2 * Copyright 2001-2020, Axel D��rfler, axeld@pinc-software.de.
3 * This file may be used under the terms of the MIT License.
4 */
5
6
7//! Block bitmap handling and allocation policies
8
9
10#include "BlockAllocator.h"
11
12#include "Debug.h"
13#include "Inode.h"
14#include "Volume.h"
15
16
17// Things the BlockAllocator should do:
18
19// - find a range of blocks of a certain size nearby a specific position
20// - allocating an unsharp range of blocks for pre-allocation
21// - free blocks
22// - know how to deal with each allocation, special handling for directories,
23//   files, symlinks, etc. (type sensitive allocation policies)
24
25// What makes the code complicated is the fact that we are not just reading
26// in the whole bitmap and operate on that in memory - e.g. a 13 GB partition
27// with a block size of 2048 bytes already has a 800kB bitmap, and the size
28// of partitions will grow even more - so that's not an option.
29// Instead we are reading in every block when it's used - since an allocation
30// group can span several blocks in the block bitmap, the AllocationBlock
31// class is there to make handling those easier.
32
33// The current implementation is only slightly optimized and could probably
34// be improved a lot. Furthermore, the allocation policies used here should
35// have some real world tests.
36
37#if BFS_TRACING && !defined(FS_SHELL)
38namespace BFSBlockTracing {
39
40
41class Allocate : public AbstractTraceEntry {
42public:
43	Allocate(block_run run)
44		:
45		fRun(run)
46	{
47		Initialized();
48	}
49
50	virtual void AddDump(TraceOutput& out)
51	{
52		out.Print("bfs:alloc %" B_PRId32 ".%" B_PRIu16 ".%" B_PRIu16,
53			fRun.AllocationGroup(), fRun.Start(), fRun.Length());
54	}
55
56	const block_run& Run() const { return fRun; }
57
58private:
59			block_run			fRun;
60};
61
62
63class Free : public AbstractTraceEntry {
64public:
65	Free(block_run run)
66		:
67		fRun(run)
68	{
69		Initialized();
70	}
71
72	virtual void AddDump(TraceOutput& out)
73	{
74		out.Print("bfs:free %" B_PRId32 ".%" B_PRIu16 ".%" B_PRIu16,
75			fRun.AllocationGroup(), fRun.Start(), fRun.Length());
76	}
77
78	const block_run& Run() const { return fRun; }
79
80private:
81			block_run			fRun;
82};
83
84
85static uint32
86checksum(const uint8* data, size_t size)
87{
88	const uint32* data4 = (const uint32*)data;
89	uint32 sum = 0;
90	while (size >= 4) {
91		sum += *data4;
92		data4++;
93		size -= 4;
94	}
95	return sum;
96}
97
98
99class Block : public AbstractTraceEntry {
100public:
101	Block(const char* label, off_t blockNumber, const uint8* data,
102			size_t size, uint32 start = 0, uint32 length = 0)
103		:
104		fBlock(blockNumber),
105		fData(data),
106		fStart(start),
107		fLength(length),
108		fLabel(label)
109	{
110		fSum = checksum(data, size);
111		Initialized();
112	}
113
114	virtual void AddDump(TraceOutput& out)
115	{
116		out.Print("bfs:%s: block %lld (%p), sum %lu, s/l %lu/%lu", fLabel,
117			fBlock, fData, fSum, fStart, fLength);
118	}
119
120private:
121			off_t				fBlock;
122			const uint8*		fData;
123			uint32				fStart;
124			uint32				fLength;
125			uint32				fSum;
126			const char*			fLabel;
127};
128
129
130class BlockChange : public AbstractTraceEntry {
131public:
132	BlockChange(const char* label, int32 block, uint32 oldData, uint32 newData)
133		:
134		fBlock(block),
135		fOldData(oldData),
136		fNewData(newData),
137		fLabel(label)
138	{
139		Initialized();
140	}
141
142	virtual void AddDump(TraceOutput& out)
143	{
144		out.Print("bfs:%s: block %ld, %08lx -> %08lx", fLabel,
145			fBlock, fOldData, fNewData);
146	}
147
148private:
149			int32				fBlock;
150			uint32				fOldData;
151			uint32				fNewData;
152			const char*			fLabel;
153};
154
155
156}	// namespace BFSBlockTracing
157
158#	define T(x) new(std::nothrow) BFSBlockTracing::x;
159#else
160#	define T(x) ;
161#endif
162
163#ifdef DEBUG_ALLOCATION_GROUPS
164#	define CHECK_ALLOCATION_GROUP(group) _CheckGroup(group)
165#else
166#	define CHECK_ALLOCATION_GROUP(group) ;
167#endif
168
169
170class AllocationBlock : public CachedBlock {
171public:
172	AllocationBlock(Volume* volume);
173
174	inline void Allocate(uint16 start, uint16 numBlocks);
175	inline void Free(uint16 start, uint16 numBlocks);
176	inline bool IsUsed(uint16 block);
177
178	status_t SetTo(AllocationGroup& group, uint16 block);
179	status_t SetToWritable(Transaction& transaction, AllocationGroup& group,
180		uint16 block);
181
182	uint32 NumBlockBits() const { return fNumBits; }
183	uint32& Block(int32 index) { return ((uint32*)fBlock)[index]; }
184	uint8* Block() const { return (uint8*)fBlock; }
185
186private:
187	uint32	fNumBits;
188#ifdef DEBUG
189	bool	fWritable;
190#endif
191};
192
193
194class AllocationGroup {
195public:
196	AllocationGroup();
197
198	void AddFreeRange(int32 start, int32 blocks);
199	bool IsFull() const { return fFreeBits == 0; }
200
201	status_t Allocate(Transaction& transaction, uint16 start, int32 length);
202	status_t Free(Transaction& transaction, uint16 start, int32 length);
203
204	uint32 NumBits() const { return fNumBits; }
205	uint32 NumBlocks() const { return fNumBlocks; }
206	int32 Start() const { return fStart; }
207
208private:
209	friend class BlockAllocator;
210
211	uint32	fNumBits;
212	uint32	fNumBlocks;
213	int32	fStart;
214	int32	fFirstFree;
215	int32	fFreeBits;
216
217	int32	fLargestStart;
218	int32	fLargestLength;
219	bool	fLargestValid;
220};
221
222
223AllocationBlock::AllocationBlock(Volume* volume)
224	: CachedBlock(volume)
225{
226}
227
228
229status_t
230AllocationBlock::SetTo(AllocationGroup& group, uint16 block)
231{
232	// 8 blocks per byte
233	fNumBits = fVolume->BlockSize() << 3;
234	// the last group may have less bits than the others
235	if ((block + 1) * fNumBits > group.NumBits())
236		fNumBits = group.NumBits() % fNumBits;
237
238#ifdef DEBUG
239	fWritable = false;
240#endif
241	return CachedBlock::SetTo(group.Start() + block);
242}
243
244
245status_t
246AllocationBlock::SetToWritable(Transaction& transaction, AllocationGroup& group,
247	uint16 block)
248{
249	// 8 blocks per byte
250	fNumBits = fVolume->BlockSize() << 3;
251	// the last group may have less bits in the last block
252	if ((block + 1) * fNumBits > group.NumBits())
253		fNumBits = group.NumBits() % fNumBits;
254
255#ifdef DEBUG
256	fWritable = true;
257#endif
258	return CachedBlock::SetToWritable(transaction, group.Start() + block);
259}
260
261
262bool
263AllocationBlock::IsUsed(uint16 block)
264{
265	if (block > fNumBits)
266		return true;
267
268	// the block bitmap is accessed in 32-bit blocks
269	return Block(block >> 5) & HOST_ENDIAN_TO_BFS_INT32(1UL << (block % 32));
270}
271
272
273void
274AllocationBlock::Allocate(uint16 start, uint16 numBlocks)
275{
276	ASSERT(start < fNumBits);
277	ASSERT(uint32(start + numBlocks) <= fNumBits);
278#ifdef DEBUG
279	ASSERT(fWritable);
280#endif
281
282	T(Block("b-alloc-in", fBlockNumber, fBlock, fVolume->BlockSize(),
283		start, numBlocks));
284
285	int32 block = start >> 5;
286
287	while (numBlocks > 0) {
288		uint32 mask = 0;
289		for (int32 i = start % 32; i < 32 && numBlocks; i++, numBlocks--)
290			mask |= 1UL << i;
291
292		T(BlockChange("b-alloc", block, Block(block),
293			Block(block) | HOST_ENDIAN_TO_BFS_INT32(mask)));
294
295#if KDEBUG
296		// check for already set blocks
297		if (HOST_ENDIAN_TO_BFS_INT32(mask) & ((uint32*)fBlock)[block]) {
298			FATAL(("AllocationBlock::Allocate(): some blocks are already "
299				"allocated, start = %u, numBlocks = %u\n", start, numBlocks));
300			panic("blocks already set!");
301		}
302#endif
303
304		Block(block++) |= HOST_ENDIAN_TO_BFS_INT32(mask);
305		start = 0;
306	}
307	T(Block("b-alloc-out", fBlockNumber, fBlock, fVolume->BlockSize(),
308		start, numBlocks));
309}
310
311
312void
313AllocationBlock::Free(uint16 start, uint16 numBlocks)
314{
315	ASSERT(start < fNumBits);
316	ASSERT(uint32(start + numBlocks) <= fNumBits);
317#ifdef DEBUG
318	ASSERT(fWritable);
319#endif
320
321	int32 block = start >> 5;
322
323	while (numBlocks > 0) {
324		uint32 mask = 0;
325		for (int32 i = start % 32; i < 32 && numBlocks; i++, numBlocks--)
326			mask |= 1UL << (i % 32);
327
328		T(BlockChange("b-free", block, Block(block),
329			Block(block) & HOST_ENDIAN_TO_BFS_INT32(~mask)));
330
331		Block(block++) &= HOST_ENDIAN_TO_BFS_INT32(~mask);
332		start = 0;
333	}
334}
335
336
337//	#pragma mark -
338
339
340/*!	The allocation groups are created and initialized in
341	BlockAllocator::Initialize() and BlockAllocator::InitializeAndClearBitmap()
342	respectively.
343*/
344AllocationGroup::AllocationGroup()
345	:
346	fFirstFree(-1),
347	fFreeBits(0),
348	fLargestValid(false)
349{
350}
351
352
353void
354AllocationGroup::AddFreeRange(int32 start, int32 blocks)
355{
356	//D(if (blocks > 512)
357	//	PRINT(("range of %ld blocks starting at %ld\n",blocks,start)));
358
359	if (fFirstFree == -1)
360		fFirstFree = start;
361
362	if (!fLargestValid || fLargestLength < blocks) {
363		fLargestStart = start;
364		fLargestLength = blocks;
365		fLargestValid = true;
366	}
367
368	fFreeBits += blocks;
369}
370
371
372/*!	Allocates the specified run in the allocation group.
373	Doesn't check if the run is valid or already allocated partially, nor
374	does it maintain the free ranges hints or the volume's used blocks count.
375	It only does the low-level work of allocating some bits in the block bitmap.
376	Assumes that the block bitmap lock is hold.
377*/
378status_t
379AllocationGroup::Allocate(Transaction& transaction, uint16 start, int32 length)
380{
381	ASSERT(start + length <= (int32)fNumBits);
382
383	// Update the allocation group info
384	// TODO: this info will be incorrect if something goes wrong later
385	// Note, the fFirstFree block doesn't have to be really free
386	if (start == fFirstFree)
387		fFirstFree = start + length;
388	fFreeBits -= length;
389
390	if (fLargestValid) {
391		bool cut = false;
392		if (fLargestStart == start) {
393			// cut from start
394			fLargestStart += length;
395			fLargestLength -= length;
396			cut = true;
397		} else if (start > fLargestStart
398			&& start < fLargestStart + fLargestLength) {
399			// cut from end
400			fLargestLength = start - fLargestStart;
401			cut = true;
402		}
403		if (cut && (fLargestLength < fLargestStart
404				|| fLargestLength
405						< (int32)fNumBits - (fLargestStart + fLargestLength))) {
406			// might not be the largest block anymore
407			fLargestValid = false;
408		}
409	}
410
411	Volume* volume = transaction.GetVolume();
412
413	// calculate block in the block bitmap and position within
414	uint32 bitsPerBlock = volume->BlockSize() << 3;
415	uint32 block = start / bitsPerBlock;
416	start = start % bitsPerBlock;
417
418	AllocationBlock cached(volume);
419
420	while (length > 0) {
421		if (cached.SetToWritable(transaction, *this, block) < B_OK) {
422			fLargestValid = false;
423			RETURN_ERROR(B_IO_ERROR);
424		}
425
426		uint32 numBlocks = length;
427		if (start + numBlocks > cached.NumBlockBits())
428			numBlocks = cached.NumBlockBits() - start;
429
430		cached.Allocate(start, numBlocks);
431
432		length -= numBlocks;
433		start = 0;
434		block++;
435	}
436
437	return B_OK;
438}
439
440
441/*!	Frees the specified run in the allocation group.
442	Doesn't check if the run is valid or was not completely allocated, nor
443	does it maintain the free ranges hints or the volume's used blocks count.
444	It only does the low-level work of freeing some bits in the block bitmap.
445	Assumes that the block bitmap lock is hold.
446*/
447status_t
448AllocationGroup::Free(Transaction& transaction, uint16 start, int32 length)
449{
450	ASSERT(start + length <= (int32)fNumBits);
451
452	// Update the allocation group info
453	// TODO: this info will be incorrect if something goes wrong later
454	if (fFirstFree > start)
455		fFirstFree = start;
456	fFreeBits += length;
457
458	// The range to be freed cannot be part of the valid largest range
459	ASSERT(!fLargestValid || start + length <= fLargestStart
460		|| start > fLargestStart);
461
462	if (fLargestValid
463		&& (start + length == fLargestStart
464			|| fLargestStart + fLargestLength == start
465			|| (start < fLargestStart && fLargestStart > fLargestLength)
466			|| (start > fLargestStart
467				&& (int32)fNumBits - (fLargestStart + fLargestLength)
468						> fLargestLength))) {
469		fLargestValid = false;
470	}
471
472	Volume* volume = transaction.GetVolume();
473
474	// calculate block in the block bitmap and position within
475	uint32 bitsPerBlock = volume->BlockSize() << 3;
476	uint32 block = start / bitsPerBlock;
477	start = start % bitsPerBlock;
478
479	AllocationBlock cached(volume);
480
481	while (length > 0) {
482		if (cached.SetToWritable(transaction, *this, block) < B_OK)
483			RETURN_ERROR(B_IO_ERROR);
484
485		T(Block("free-1", block, cached.Block(), volume->BlockSize()));
486		uint16 freeLength = length;
487		if (uint32(start + length) > cached.NumBlockBits())
488			freeLength = cached.NumBlockBits() - start;
489
490		cached.Free(start, freeLength);
491
492		length -= freeLength;
493		start = 0;
494		T(Block("free-2", block, cached.Block(), volume->BlockSize()));
495		block++;
496	}
497	return B_OK;
498}
499
500
501//	#pragma mark -
502
503
504BlockAllocator::BlockAllocator(Volume* volume)
505	:
506	fVolume(volume),
507	fGroups(NULL)
508	//fCheckBitmap(NULL),
509	//fCheckCookie(NULL)
510{
511	recursive_lock_init(&fLock, "bfs allocator");
512}
513
514
515BlockAllocator::~BlockAllocator()
516{
517	recursive_lock_destroy(&fLock);
518	delete[] fGroups;
519}
520
521
522status_t
523BlockAllocator::Initialize(bool full)
524{
525	fNumGroups = fVolume->AllocationGroups();
526	fBlocksPerGroup = fVolume->SuperBlock().BlocksPerAllocationGroup();
527	//fNumBlocks = (fVolume->NumBlocks() + fVolume->BlockSize() * 8 - 1)
528		/// (fVolume->BlockSize() * 8);
529	fNumBlocks = fVolume->NumBitmapBlocks();
530
531	fGroups = new(std::nothrow) AllocationGroup[fNumGroups];
532	if (fGroups == NULL)
533		return B_NO_MEMORY;
534
535	if (!full)
536		return B_OK;
537
538	recursive_lock_lock(&fLock);
539		// the lock will be released by the _Initialize() method
540
541	thread_id id = spawn_kernel_thread((thread_func)BlockAllocator::_Initialize,
542		"bfs block allocator", B_LOW_PRIORITY, this);
543	if (id < B_OK)
544		return _Initialize(this);
545
546	recursive_lock_transfer_lock(&fLock, id);
547
548	return resume_thread(id);
549}
550
551
552status_t
553BlockAllocator::InitializeAndClearBitmap(Transaction& transaction)
554{
555	status_t status = Initialize(false);
556	if (status != B_OK)
557		return status;
558
559	uint32 numBits = 8 * fBlocksPerGroup * fVolume->BlockSize();
560	uint32 blockShift = fVolume->BlockShift();
561
562	uint32* buffer = (uint32*)malloc(numBits >> 3);
563	if (buffer == NULL)
564		RETURN_ERROR(B_NO_MEMORY);
565
566	memset(buffer, 0, numBits >> 3);
567
568	off_t offset = 1;
569		// the bitmap starts directly after the superblock
570
571	// initialize the AllocationGroup objects and clear the on-disk bitmap
572
573	for (int32 i = 0; i < fNumGroups; i++) {
574		if (write_pos(fVolume->Device(), offset << blockShift, buffer,
575				fBlocksPerGroup << blockShift) < B_OK) {
576			free(buffer);
577			return B_ERROR;
578		}
579
580		// the last allocation group may contain less blocks than the others
581		if (i == fNumGroups - 1) {
582			fGroups[i].fNumBits = fVolume->NumBlocks() - i * numBits;
583			fGroups[i].fNumBlocks = 1 + ((fGroups[i].NumBits() - 1)
584				>> (blockShift + 3));
585		} else {
586			fGroups[i].fNumBits = numBits;
587			fGroups[i].fNumBlocks = fBlocksPerGroup;
588		}
589		fGroups[i].fStart = offset;
590		fGroups[i].fFirstFree = fGroups[i].fLargestStart = 0;
591		fGroups[i].fFreeBits = fGroups[i].fLargestLength = fGroups[i].fNumBits;
592		fGroups[i].fLargestValid = true;
593
594		offset += fBlocksPerGroup;
595	}
596	free(buffer);
597
598	// reserve the boot block, the log area, and the block bitmap itself
599	uint32 reservedBlocks = fVolume->ToBlock(fVolume->Log()) + fVolume->Log().Length();
600	uint32 blocksToReserve = reservedBlocks;
601	for (int32 i = 0; i < fNumGroups; i++) {
602		int32 reservedBlocksInGroup = min_c(blocksToReserve, numBits);
603		if (fGroups[i].Allocate(transaction, 0, reservedBlocksInGroup) < B_OK) {
604			FATAL(("could not allocate reserved space for block bitmap/log!\n"));
605			return B_ERROR;
606		}
607		blocksToReserve -= reservedBlocksInGroup;
608		if (blocksToReserve == 0)
609			break;
610	}
611	fVolume->SuperBlock().used_blocks
612		= HOST_ENDIAN_TO_BFS_INT64(reservedBlocks);
613
614	return B_OK;
615}
616
617
618status_t
619BlockAllocator::_Initialize(BlockAllocator* allocator)
620{
621	// The lock must already be held at this point
622	RecursiveLocker locker(allocator->fLock, true);
623
624	Volume* volume = allocator->fVolume;
625	uint32 blocks = allocator->fBlocksPerGroup;
626	uint32 blockShift = volume->BlockShift();
627	off_t freeBlocks = 0;
628
629	uint32* buffer = (uint32*)malloc(blocks << blockShift);
630	if (buffer == NULL)
631		RETURN_ERROR(B_NO_MEMORY);
632
633	AllocationGroup* groups = allocator->fGroups;
634	off_t offset = 1;
635	uint32 bitsPerGroup = 8 * (blocks << blockShift);
636	int32 numGroups = allocator->fNumGroups;
637
638	for (int32 i = 0; i < numGroups; i++) {
639		if (read_pos(volume->Device(), offset << blockShift, buffer,
640				blocks << blockShift) < B_OK)
641			break;
642
643		// the last allocation group may contain less blocks than the others
644		if (i == numGroups - 1) {
645			groups[i].fNumBits = volume->NumBlocks() - i * bitsPerGroup;
646			groups[i].fNumBlocks = 1 + ((groups[i].NumBits() - 1)
647				>> (blockShift + 3));
648		} else {
649			groups[i].fNumBits = bitsPerGroup;
650			groups[i].fNumBlocks = blocks;
651		}
652		groups[i].fStart = offset;
653
654		// finds all free ranges in this allocation group
655		int32 start = -1, range = 0;
656		int32 numBits = groups[i].fNumBits, bit = 0;
657		int32 count = (numBits + 31) / 32;
658
659		for (int32 k = 0; k < count; k++) {
660			for (int32 j = 0; j < 32 && bit < numBits; j++, bit++) {
661				if (buffer[k] & (1UL << j)) {
662					// block is in use
663					if (range > 0) {
664						groups[i].AddFreeRange(start, range);
665						range = 0;
666					}
667				} else if (range++ == 0) {
668					// block is free, start new free range
669					start = bit;
670				}
671			}
672		}
673		if (range)
674			groups[i].AddFreeRange(start, range);
675
676		freeBlocks += groups[i].fFreeBits;
677
678		offset += blocks;
679	}
680	free(buffer);
681
682	// check if block bitmap and log area are reserved
683	uint32 reservedBlocks = volume->ToBlock(volume->Log()) + volume->Log().Length();
684
685	if (allocator->CheckBlocks(0, reservedBlocks) != B_OK) {
686		if (volume->IsReadOnly()) {
687			FATAL(("Space for block bitmap or log area is not reserved "
688				"(volume is mounted read-only)!\n"));
689		} else {
690			Transaction transaction(volume, 0);
691			if (groups[0].Allocate(transaction, 0, reservedBlocks) != B_OK) {
692				FATAL(("Could not allocate reserved space for block "
693					"bitmap/log!\n"));
694				volume->Panic();
695			} else {
696				transaction.Done();
697				FATAL(("Space for block bitmap or log area was not "
698					"reserved!\n"));
699			}
700		}
701	}
702
703	off_t usedBlocks = volume->NumBlocks() - freeBlocks;
704	if (volume->UsedBlocks() != usedBlocks) {
705		// If the disk in a dirty state at mount time, it's
706		// normal that the values don't match
707		INFORM(("volume reports %" B_PRIdOFF " used blocks, correct is %"
708			B_PRIdOFF "\n", volume->UsedBlocks(), usedBlocks));
709		volume->SuperBlock().used_blocks = HOST_ENDIAN_TO_BFS_INT64(usedBlocks);
710	}
711
712	return B_OK;
713}
714
715
716void
717BlockAllocator::Uninitialize()
718{
719	// We only have to make sure that the initializer thread isn't running
720	// anymore.
721	recursive_lock_lock(&fLock);
722}
723
724
725/*!	Tries to allocate between \a minimum, and \a maximum blocks starting
726	at group \a groupIndex with offset \a start. The resulting allocation
727	is put into \a run.
728
729	The number of allocated blocks is always a multiple of \a minimum which
730	has to be a power of two value.
731*/
732status_t
733BlockAllocator::AllocateBlocks(Transaction& transaction, int32 groupIndex,
734	uint16 start, uint16 maximum, uint16 minimum, block_run& run)
735{
736	if (maximum == 0)
737		return B_BAD_VALUE;
738
739	FUNCTION_START(("group = %" B_PRId32 ", start = %" B_PRIu16
740		", maximum = %" B_PRIu16 ", minimum = %" B_PRIu16 "\n",
741		groupIndex, start, maximum, minimum));
742
743	AllocationBlock cached(fVolume);
744	RecursiveLocker lock(fLock);
745
746	uint32 bitsPerFullBlock = fVolume->BlockSize() << 3;
747
748	// Find the block_run that can fulfill the request best
749	int32 bestGroup = -1;
750	int32 bestStart = -1;
751	int32 bestLength = -1;
752
753	for (int32 i = 0; i < fNumGroups + 1; i++, groupIndex++, start = 0) {
754		groupIndex = groupIndex % fNumGroups;
755		AllocationGroup& group = fGroups[groupIndex];
756
757		CHECK_ALLOCATION_GROUP(groupIndex);
758
759		if (start >= group.NumBits() || group.IsFull())
760			continue;
761
762		// The wanted maximum is smaller than the largest free block in the
763		// group or already smaller than the minimum
764
765		if (start < group.fFirstFree)
766			start = group.fFirstFree;
767
768		if (group.fLargestValid) {
769			if (group.fLargestLength < bestLength)
770				continue;
771
772			if (group.fLargestStart >= start) {
773				if (group.fLargestLength >= bestLength) {
774					bestGroup = groupIndex;
775					bestStart = group.fLargestStart;
776					bestLength = group.fLargestLength;
777
778					if (bestLength >= maximum)
779						break;
780				}
781
782				// We know everything about this group we have to, let's skip
783				// to the next
784				continue;
785			}
786		}
787
788		// There may be more than one block per allocation group - and
789		// we iterate through it to find a place for the allocation.
790		// (one allocation can't exceed one allocation group)
791
792		uint32 block = start / (fVolume->BlockSize() << 3);
793		int32 currentStart = 0, currentLength = 0;
794		int32 groupLargestStart = -1;
795		int32 groupLargestLength = -1;
796		int32 currentBit = start;
797		bool canFindGroupLargest = start == 0;
798
799		for (; block < group.NumBlocks(); block++) {
800			if (cached.SetTo(group, block) < B_OK)
801				RETURN_ERROR(B_ERROR);
802
803			T(Block("alloc-in", group.Start() + block, cached.Block(),
804				fVolume->BlockSize(), groupIndex, currentStart));
805
806			// find a block large enough to hold the allocation
807			for (uint32 bit = start % bitsPerFullBlock;
808					bit < cached.NumBlockBits(); bit++) {
809				if (!cached.IsUsed(bit)) {
810					if (currentLength == 0) {
811						// start new range
812						currentStart = currentBit;
813					}
814
815					// have we found a range large enough to hold numBlocks?
816					if (++currentLength >= maximum) {
817						bestGroup = groupIndex;
818						bestStart = currentStart;
819						bestLength = currentLength;
820						break;
821					}
822				} else {
823					if (currentLength) {
824						// end of a range
825						if (currentLength > bestLength) {
826							bestGroup = groupIndex;
827							bestStart = currentStart;
828							bestLength = currentLength;
829						}
830						if (currentLength > groupLargestLength) {
831							groupLargestStart = currentStart;
832							groupLargestLength = currentLength;
833						}
834						currentLength = 0;
835					}
836					if ((int32)group.NumBits() - currentBit
837							<= groupLargestLength) {
838						// We can't find a bigger block in this group anymore,
839						// let's skip the rest.
840						block = group.NumBlocks();
841						break;
842					}
843				}
844				currentBit++;
845			}
846
847			T(Block("alloc-out", block, cached.Block(),
848				fVolume->BlockSize(), groupIndex, currentStart));
849
850			if (bestLength >= maximum) {
851				canFindGroupLargest = false;
852				break;
853			}
854
855			// start from the beginning of the next block
856			start = 0;
857		}
858
859		if (currentBit == (int32)group.NumBits()) {
860			if (currentLength > bestLength) {
861				bestGroup = groupIndex;
862				bestStart = currentStart;
863				bestLength = currentLength;
864			}
865			if (canFindGroupLargest && currentLength > groupLargestLength) {
866				groupLargestStart = currentStart;
867				groupLargestLength = currentLength;
868			}
869		}
870
871		if (canFindGroupLargest && !group.fLargestValid
872			&& groupLargestLength >= 0) {
873			group.fLargestStart = groupLargestStart;
874			group.fLargestLength = groupLargestLength;
875			group.fLargestValid = true;
876		}
877
878		if (bestLength >= maximum)
879			break;
880	}
881
882	// If we found a suitable range, mark the blocks as in use, and
883	// write the updated block bitmap back to disk
884	if (bestLength < minimum)
885		return B_DEVICE_FULL;
886
887	if (bestLength > maximum)
888		bestLength = maximum;
889	else if (minimum > 1) {
890		// make sure bestLength is a multiple of minimum
891		bestLength = round_down(bestLength, minimum);
892	}
893
894	if (fGroups[bestGroup].Allocate(transaction, bestStart, bestLength) != B_OK)
895		RETURN_ERROR(B_IO_ERROR);
896
897	CHECK_ALLOCATION_GROUP(bestGroup);
898
899	run.allocation_group = HOST_ENDIAN_TO_BFS_INT32(bestGroup);
900	run.start = HOST_ENDIAN_TO_BFS_INT16(bestStart);
901	run.length = HOST_ENDIAN_TO_BFS_INT16(bestLength);
902
903	fVolume->SuperBlock().used_blocks
904		= HOST_ENDIAN_TO_BFS_INT64(fVolume->UsedBlocks() + bestLength);
905		// We are not writing back the disk's superblock - it's
906		// either done by the journaling code, or when the disk
907		// is unmounted.
908		// If the value is not correct at mount time, it will be
909		// fixed anyway.
910
911	// We need to flush any remaining blocks in the new allocation to make sure
912	// they won't interfere with the file cache.
913	block_cache_discard(fVolume->BlockCache(), fVolume->ToBlock(run),
914		run.Length());
915
916	T(Allocate(run));
917	return B_OK;
918}
919
920
921status_t
922BlockAllocator::AllocateForInode(Transaction& transaction,
923	const block_run* parent, mode_t type, block_run& run)
924{
925	// Apply some allocation policies here (AllocateBlocks() will break them
926	// if necessary) - we will start with those described in Dominic Giampaolo's
927	// "Practical File System Design", and see how good they work
928
929	// Files are going in the same allocation group as its parent,
930	// sub-directories will be inserted 8 allocation groups after
931	// the one of the parent
932	uint16 group = parent->AllocationGroup();
933	if ((type & (S_DIRECTORY | S_INDEX_DIR | S_ATTR_DIR)) == S_DIRECTORY)
934		group += 8;
935
936	return AllocateBlocks(transaction, group, 0, 1, 1, run);
937}
938
939
940status_t
941BlockAllocator::Allocate(Transaction& transaction, Inode* inode,
942	off_t numBlocks, block_run& run, uint16 minimum)
943{
944	if (numBlocks <= 0)
945		return B_ERROR;
946
947	// one block_run can't hold more data than there is in one allocation group
948	if (numBlocks > fGroups[0].NumBits())
949		numBlocks = fGroups[0].NumBits();
950
951	// since block_run.length is uint16, the largest number of blocks that
952	// can be covered by a block_run is 65535
953	// TODO: if we drop compatibility, couldn't we do this any better?
954	// There are basically two possibilities:
955	// a) since a length of zero doesn't have any sense, take that for 65536 -
956	//    but that could cause many problems (bugs) in other areas
957	// b) reduce the maximum amount of blocks per block_run, so that the
958	//    remaining number of free blocks can be used in a useful manner
959	//    (like 4 blocks) - but that would also reduce the maximum file size
960	// c) have BlockRun::Length() return (length + 1).
961	if (numBlocks > MAX_BLOCK_RUN_LENGTH)
962		numBlocks = MAX_BLOCK_RUN_LENGTH;
963
964	// Apply some allocation policies here (AllocateBlocks() will break them
965	// if necessary)
966	uint16 group = inode->BlockRun().AllocationGroup();
967	uint16 start = 0;
968
969	// Are there already allocated blocks? (then just try to allocate near the
970	// last one)
971	if (inode->Size() > 0) {
972		const data_stream& data = inode->Node().data;
973		// TODO: we currently don't care for when the data stream
974		// is already grown into the indirect ranges
975		if (data.max_double_indirect_range == 0
976			&& data.max_indirect_range == 0) {
977			// Since size > 0, there must be a valid block run in this stream
978			int32 last = 0;
979			for (; last < NUM_DIRECT_BLOCKS - 1; last++)
980				if (data.direct[last + 1].IsZero())
981					break;
982
983			group = data.direct[last].AllocationGroup();
984			start = data.direct[last].Start() + data.direct[last].Length();
985		}
986	} else if (inode->IsContainer() || inode->IsSymLink()) {
987		// directory and symbolic link data will go in the same allocation
988		// group as the inode is in but after the inode data
989		start = inode->BlockRun().Start();
990	} else {
991		// file data will start in the next allocation group
992		group = inode->BlockRun().AllocationGroup() + 1;
993	}
994
995	return AllocateBlocks(transaction, group, start, numBlocks, minimum, run);
996}
997
998
999status_t
1000BlockAllocator::Free(Transaction& transaction, block_run run)
1001{
1002	RecursiveLocker lock(fLock);
1003
1004	int32 group = run.AllocationGroup();
1005	uint16 start = run.Start();
1006	uint16 length = run.Length();
1007
1008	FUNCTION_START(("group = %" B_PRId32 ", start = %" B_PRIu16
1009		", length = %" B_PRIu16 "\n", group, start, length))
1010	T(Free(run));
1011
1012	// doesn't use Volume::IsValidBlockRun() here because it can check better
1013	// against the group size (the last group may have a different length)
1014	if (group < 0 || group >= fNumGroups
1015		|| start > fGroups[group].NumBits()
1016		|| uint32(start + length) > fGroups[group].NumBits()
1017		|| length == 0) {
1018		FATAL(("tried to free an invalid block_run"
1019			" (%" B_PRId32 ", %" B_PRIu16 ", %" B_PRIu16")\n",
1020			group, start, length));
1021		DEBUGGER(("tried to free invalid block_run"));
1022		return B_BAD_VALUE;
1023	}
1024	// check if someone tries to free reserved areas at the beginning of the
1025	// drive
1026	if (group < fVolume->Log().AllocationGroup()
1027		|| (group == fVolume->Log().AllocationGroup()
1028			&& start < uint32(fVolume->Log().Start()) + fVolume->Log().Length())) {
1029		FATAL(("tried to free a reserved block_run"
1030			" (%" B_PRId32 ", %" B_PRIu16 ", %" B_PRIu16")\n",
1031			group, start, length));
1032		DEBUGGER(("tried to free reserved block"));
1033		return B_BAD_VALUE;
1034	}
1035#ifdef DEBUG
1036	if (CheckBlockRun(run) != B_OK)
1037		return B_BAD_DATA;
1038#endif
1039
1040	CHECK_ALLOCATION_GROUP(group);
1041
1042	if (fGroups[group].Free(transaction, start, length) != B_OK)
1043		RETURN_ERROR(B_IO_ERROR);
1044
1045	CHECK_ALLOCATION_GROUP(group);
1046
1047#ifdef DEBUG
1048	if (CheckBlockRun(run, NULL, false) != B_OK) {
1049		DEBUGGER(("CheckBlockRun() reports allocated blocks (which were just "
1050			"freed)\n"));
1051	}
1052#endif
1053
1054	fVolume->SuperBlock().used_blocks =
1055		HOST_ENDIAN_TO_BFS_INT64(fVolume->UsedBlocks() - run.Length());
1056	return B_OK;
1057}
1058
1059
1060#ifdef DEBUG_FRAGMENTER
1061void
1062BlockAllocator::Fragment()
1063{
1064	AllocationBlock cached(fVolume);
1065	RecursiveLocker lock(fLock);
1066
1067	// only leave 4 block holes
1068	static const uint32 kMask = 0x0f0f0f0f;
1069	uint32 valuesPerBlock = fVolume->BlockSize() / 4;
1070
1071	for (int32 i = 0; i < fNumGroups; i++) {
1072		AllocationGroup& group = fGroups[i];
1073
1074		for (uint32 block = 0; block < group.NumBlocks(); block++) {
1075			Transaction transaction(fVolume, 0);
1076
1077			if (cached.SetToWritable(transaction, group, block) != B_OK)
1078				return;
1079
1080			for (int32 index = 0; index < valuesPerBlock; index++) {
1081				cached.Block(index) |= HOST_ENDIAN_TO_BFS_INT32(kMask);
1082			}
1083
1084			transaction.Done();
1085		}
1086	}
1087}
1088#endif	// DEBUG_FRAGMENTER
1089
1090
1091#ifdef DEBUG_ALLOCATION_GROUPS
1092void
1093BlockAllocator::_CheckGroup(int32 groupIndex) const
1094{
1095	AllocationBlock cached(fVolume);
1096	ASSERT_LOCKED_RECURSIVE(&fLock);
1097
1098	AllocationGroup& group = fGroups[groupIndex];
1099
1100	int32 currentStart = 0, currentLength = 0;
1101	int32 firstFree = -1;
1102	int32 largestStart = -1;
1103	int32 largestLength = 0;
1104	int32 currentBit = 0;
1105
1106	for (uint32 block = 0; block < group.NumBlocks(); block++) {
1107		if (cached.SetTo(group, block) < B_OK) {
1108			panic("setting group block %d failed\n", (int)block);
1109			return;
1110		}
1111
1112		for (uint32 bit = 0; bit < cached.NumBlockBits(); bit++) {
1113			if (!cached.IsUsed(bit)) {
1114				if (firstFree < 0) {
1115					firstFree = currentBit;
1116					if (!group.fLargestValid) {
1117						if (firstFree >= 0 && firstFree < group.fFirstFree) {
1118							// mostly harmless but noteworthy
1119							dprintf("group %d first free too late: should be "
1120								"%d, is %d\n", (int)groupIndex, (int)firstFree,
1121								(int)group.fFirstFree);
1122						}
1123						return;
1124					}
1125				}
1126
1127				if (currentLength == 0) {
1128					// start new range
1129					currentStart = currentBit;
1130				}
1131				currentLength++;
1132			} else if (currentLength) {
1133				// end of a range
1134				if (currentLength > largestLength) {
1135					largestStart = currentStart;
1136					largestLength = currentLength;
1137				}
1138				currentLength = 0;
1139			}
1140			currentBit++;
1141		}
1142	}
1143
1144	if (currentLength > largestLength) {
1145		largestStart = currentStart;
1146		largestLength = currentLength;
1147	}
1148
1149	if (firstFree >= 0 && firstFree < group.fFirstFree) {
1150		// mostly harmless but noteworthy
1151		dprintf("group %d first free too late: should be %d, is %d\n",
1152			(int)groupIndex, (int)firstFree, (int)group.fFirstFree);
1153	}
1154	if (group.fLargestValid
1155		&& (largestStart != group.fLargestStart
1156			|| largestLength != group.fLargestLength)) {
1157		panic("bfs %p: group %d largest differs: %d.%d, checked %d.%d.\n",
1158			fVolume, (int)groupIndex, (int)group.fLargestStart,
1159			(int)group.fLargestLength, (int)largestStart, (int)largestLength);
1160	}
1161}
1162#endif	// DEBUG_ALLOCATION_GROUPS
1163
1164
1165status_t
1166BlockAllocator::Trim(uint64 offset, uint64 size, uint64& trimmedSize)
1167{
1168	// TODO: Remove this check when offset and size handling is implemented
1169	if (offset != 0
1170		|| fVolume->NumBlocks() < 0
1171		|| size < (uint64)fVolume->NumBlocks() * fVolume->BlockSize()) {
1172		INFORM(("BFS Trim: Ranges smaller than the file system size"
1173			" are not supported yet.\n"));
1174		return B_UNSUPPORTED;
1175	}
1176
1177	const uint32 kTrimRanges = 128;
1178	fs_trim_data* trimData = (fs_trim_data*)malloc(sizeof(fs_trim_data)
1179		+ 2 * sizeof(uint64) * (kTrimRanges - 1));
1180	if (trimData == NULL)
1181		return B_NO_MEMORY;
1182
1183	MemoryDeleter deleter(trimData);
1184	RecursiveLocker locker(fLock);
1185
1186	// TODO: take given offset and size into account!
1187	int32 lastGroup = fNumGroups - 1;
1188	uint32 firstBlock = 0;
1189	uint32 firstBit = 0;
1190	uint64 currentBlock = 0;
1191	uint32 blockShift = fVolume->BlockShift();
1192
1193	uint64 firstFree = 0;
1194	uint64 freeLength = 0;
1195
1196	trimData->range_count = 0;
1197	trimmedSize = 0;
1198
1199	AllocationBlock cached(fVolume);
1200	for (int32 groupIndex = 0; groupIndex <= lastGroup; groupIndex++) {
1201		AllocationGroup& group = fGroups[groupIndex];
1202
1203		for (uint32 block = firstBlock; block < group.NumBlocks(); block++) {
1204			cached.SetTo(group, block);
1205
1206			for (uint32 i = firstBit; i < cached.NumBlockBits(); i++) {
1207				if (cached.IsUsed(i)) {
1208					// Block is in use
1209					if (freeLength > 0) {
1210						// Overflow is unlikely to happen, but check it anyway
1211						if ((firstFree << blockShift) >> blockShift
1212								!= firstFree
1213							|| (freeLength << blockShift) >> blockShift
1214								!= freeLength) {
1215							FATAL(("BlockAllocator::Trim:"
1216								" Overflow detected!\n"));
1217							return B_ERROR;
1218						}
1219						status_t status = _TrimNext(*trimData, kTrimRanges,
1220							firstFree << blockShift, freeLength << blockShift,
1221							false, trimmedSize);
1222						if (status != B_OK)
1223							return status;
1224
1225						freeLength = 0;
1226					}
1227				} else if (freeLength++ == 0) {
1228					// Block is free, start new free range
1229					firstFree = currentBlock;
1230				}
1231
1232				currentBlock++;
1233			}
1234		}
1235
1236		firstBlock = 0;
1237		firstBit = 0;
1238	}
1239
1240	return _TrimNext(*trimData, kTrimRanges, firstFree << blockShift,
1241		freeLength << blockShift, true, trimmedSize);
1242}
1243
1244
1245//	#pragma mark -
1246
1247
1248/*!	Checks whether or not the specified block range is allocated or not,
1249	depending on the \a allocated argument.
1250*/
1251status_t
1252BlockAllocator::CheckBlocks(off_t start, off_t length, bool allocated,
1253	off_t* firstError)
1254{
1255	if (start < 0 || start + length > fVolume->NumBlocks())
1256		return B_BAD_VALUE;
1257
1258	off_t block = start;
1259
1260	int32 group = start >> fVolume->AllocationGroupShift();
1261	uint32 bitmapBlock = start / (fVolume->BlockSize() << 3);
1262	uint32 blockOffset = start % (fVolume->BlockSize() << 3);
1263
1264	uint32 groupBlock = bitmapBlock % fBlocksPerGroup;
1265
1266	AllocationBlock cached(fVolume);
1267
1268	while (groupBlock < fGroups[group].NumBlocks() && length > 0) {
1269		if (cached.SetTo(fGroups[group], groupBlock) != B_OK)
1270			RETURN_ERROR(B_IO_ERROR);
1271
1272		for (; blockOffset < cached.NumBlockBits() && length > 0;
1273				blockOffset++, length--, block++) {
1274			if (cached.IsUsed(blockOffset) != allocated) {
1275				PRINT(("CheckBlocks: Erroneous block (group = %" B_PRId32
1276					", groupBlock = %" B_PRIu32
1277					", blockOffset = %" B_PRIu32 ")!\n",
1278					group, groupBlock, blockOffset));
1279
1280				if (firstError)
1281					*firstError = block;
1282
1283				return B_BAD_DATA;
1284			}
1285		}
1286
1287		blockOffset = 0;
1288
1289		if (++groupBlock >= fGroups[group].NumBlocks()) {
1290			groupBlock = 0;
1291			group++;
1292		}
1293	}
1294
1295	return B_OK;
1296}
1297
1298
1299bool
1300BlockAllocator::IsValidBlockRun(block_run run, const char* type)
1301{
1302	if (run.AllocationGroup() < 0 || run.AllocationGroup() >= fNumGroups
1303		|| run.Start() > fGroups[run.AllocationGroup()].fNumBits
1304		|| uint32(run.Start() + run.Length())
1305				> fGroups[run.AllocationGroup()].fNumBits
1306		|| run.length == 0) {
1307		PRINT(("%s: block_run(%" B_PRId32 ", %" B_PRIu16 ", %" B_PRIu16")"
1308			" is invalid!\n", type, run.AllocationGroup(), run.Start(),
1309			run.Length()));
1310		return false;
1311	}
1312	return true;
1313}
1314
1315
1316status_t
1317BlockAllocator::CheckBlockRun(block_run run, const char* type, bool allocated)
1318{
1319	if (!IsValidBlockRun(run, type))
1320		return B_BAD_DATA;
1321
1322	status_t status = CheckBlocks(fVolume->ToBlock(run), run.Length(),
1323		allocated);
1324	if (status != B_OK) {
1325		PRINT(("%s: block_run(%" B_PRId32 ", %" B_PRIu16 ", %" B_PRIu16")"
1326			" is only partially allocated!\n", type, run.AllocationGroup(),
1327			run.Start(), run.Length()));
1328	}
1329
1330	return status;
1331}
1332
1333
1334bool
1335BlockAllocator::_AddTrim(fs_trim_data& trimData, uint32 maxRanges,
1336	uint64 offset, uint64 size)
1337{
1338	ASSERT(trimData.range_count < maxRanges);
1339	if (size == 0)
1340		return false;
1341
1342	trimData.ranges[trimData.range_count].offset = offset;
1343	trimData.ranges[trimData.range_count].size = size;
1344	trimData.range_count++;
1345
1346	return (trimData.range_count == maxRanges);
1347}
1348
1349
1350status_t
1351BlockAllocator::_TrimNext(fs_trim_data& trimData, uint32 maxRanges,
1352	uint64 offset, uint64 size, bool force, uint64& trimmedSize)
1353{
1354	PRINT(("_TrimNext(index %" B_PRIu32 ", offset %" B_PRIu64 ", size %"
1355		B_PRIu64 ")\n", trimData.range_count, offset, size));
1356
1357	const bool rangesFilled = _AddTrim(trimData, maxRanges, offset, size);
1358
1359	if (rangesFilled || force) {
1360		// Trim now
1361		trimData.trimmed_size = 0;
1362#ifdef DEBUG_TRIM
1363		dprintf("TRIM: BFS: free ranges (bytes):\n");
1364		for (uint32 i = 0; i < trimData.range_count; i++) {
1365			dprintf("[%3" B_PRIu32 "] %" B_PRIu64 " : %" B_PRIu64 "\n", i,
1366				trimData.ranges[i].offset, trimData.ranges[i].size);
1367		}
1368#endif
1369		if (ioctl(fVolume->Device(), B_TRIM_DEVICE, &trimData,
1370				sizeof(fs_trim_data)
1371					+ 2 * sizeof(uint64) * (trimData.range_count - 1)) != 0) {
1372			return errno;
1373		}
1374
1375		trimmedSize += trimData.trimmed_size;
1376		trimData.range_count = 0;
1377	}
1378
1379	return B_OK;
1380}
1381
1382
1383//	#pragma mark - debugger commands
1384
1385
1386#ifdef BFS_DEBUGGER_COMMANDS
1387
1388void
1389BlockAllocator::Dump(int32 index)
1390{
1391	kprintf("allocation groups: %" B_PRId32 " (base %p)\n", fNumGroups, fGroups);
1392	kprintf("blocks per group: %" B_PRId32 "\n", fBlocksPerGroup);
1393
1394	for (int32 i = 0; i < fNumGroups; i++) {
1395		if (index != -1 && i != index)
1396			continue;
1397
1398		AllocationGroup& group = fGroups[i];
1399
1400		kprintf("[%3" B_PRId32 "] num bits:       %" B_PRIu32 "  (%p)\n", i,
1401			group.NumBits(), &group);
1402		kprintf("      num blocks:     %" B_PRIu32 "\n", group.NumBlocks());
1403		kprintf("      start:          %" B_PRId32 "\n", group.Start());
1404		kprintf("      first free:     %" B_PRId32 "\n", group.fFirstFree);
1405		kprintf("      largest start:  %" B_PRId32 "%s\n", group.fLargestStart,
1406			group.fLargestValid ? "" : "  (invalid)");
1407		kprintf("      largest length: %" B_PRId32 "\n", group.fLargestLength);
1408		kprintf("      free bits:      %" B_PRId32 "\n", group.fFreeBits);
1409	}
1410}
1411
1412
1413#if BFS_TRACING
1414static char kTraceBuffer[256];
1415
1416
1417int
1418dump_block_allocator_blocks(int argc, char** argv)
1419{
1420	if (argc != 3 || !strcmp(argv[1], "--help")) {
1421		kprintf("usage: %s <ptr-to-volume> <block>\n", argv[0]);
1422		return 0;
1423	}
1424
1425	Volume* volume = (Volume*)parse_expression(argv[1]);
1426	off_t block = parse_expression(argv[2]);
1427
1428	// iterate over all tracing entries to find overlapping actions
1429
1430	using namespace BFSBlockTracing;
1431
1432	LazyTraceOutput out(kTraceBuffer, sizeof(kTraceBuffer), 0);
1433	TraceEntryIterator iterator;
1434	while (TraceEntry* _entry = iterator.Next()) {
1435		if (Allocate* entry = dynamic_cast<Allocate*>(_entry)) {
1436			off_t first = volume->ToBlock(entry->Run());
1437			off_t last = first - 1 + entry->Run().Length();
1438			if (block >= first && block <= last) {
1439				out.Clear();
1440				const char* dump = out.DumpEntry(entry);
1441				kprintf("%5ld. %s\n", iterator.Index(), dump);
1442			}
1443		} else if (Free* entry = dynamic_cast<Free*>(_entry)) {
1444			off_t first = volume->ToBlock(entry->Run());
1445			off_t last = first - 1 + entry->Run().Length();
1446			if (block >= first && block <= last) {
1447				out.Clear();
1448				const char* dump = out.DumpEntry(entry);
1449				kprintf("%5ld. %s\n", iterator.Index(), dump);
1450			}
1451		}
1452	}
1453
1454	return 0;
1455}
1456#endif
1457
1458
1459int
1460dump_block_allocator(int argc, char** argv)
1461{
1462	int32 group = -1;
1463	if (argc == 3) {
1464		group = parse_expression(argv[2]);
1465		argc--;
1466	}
1467
1468	if (argc != 2 || !strcmp(argv[1], "--help")) {
1469		kprintf("usage: %s <ptr-to-volume> [group]\n", argv[0]);
1470		return 0;
1471	}
1472
1473	Volume* volume = (Volume*)parse_expression(argv[1]);
1474	BlockAllocator& allocator = volume->Allocator();
1475
1476	allocator.Dump(group);
1477	return 0;
1478}
1479
1480#endif	// BFS_DEBUGGER_COMMANDS
1481
1482