xref: /haiku/src/add-ons/kernel/file_systems/bfs/CheckVisitor.cpp (revision 4a55cc230cf7566cadcbb23b1928eefff8aea9a2)
1 /*
2  * Copyright 2002-2020, Axel Dörfler, axeld@pinc-software.de.
3  * Copyright 2012, Andreas Henriksson, sausageboy@gmail.com
4  * This file may be used under the terms of the MIT License.
5  */
6 
7 
8 //! File system error checking
9 
10 
11 #include "CheckVisitor.h"
12 
13 #include "BlockAllocator.h"
14 #include "BPlusTree.h"
15 #include "Inode.h"
16 #include "Volume.h"
17 
18 
19 struct check_index {
20 	check_index()
21 		:
22 		inode(NULL)
23 	{
24 	}
25 
26 	char				name[B_FILE_NAME_LENGTH];
27 	block_run			run;
28 	Inode*				inode;
29 };
30 
31 
32 CheckVisitor::CheckVisitor(Volume* volume)
33 	:
34 	FileSystemVisitor(volume),
35 	fCheckBitmap(NULL)
36 {
37 }
38 
39 
40 CheckVisitor::~CheckVisitor()
41 {
42 	free(fCheckBitmap);
43 }
44 
45 
46 status_t
47 CheckVisitor::StartBitmapPass()
48 {
49 	if (!_ControlValid())
50 		return B_BAD_VALUE;
51 
52 	// Lock the volume's journal and block allocator
53 	GetVolume()->GetJournal(0)->Lock(NULL, true);
54 	recursive_lock_lock(&GetVolume()->Allocator().Lock());
55 
56 	size_t size = _BitmapSize();
57 	fCheckBitmap = (uint32*)malloc(size);
58 	if (fCheckBitmap == NULL) {
59 		recursive_lock_unlock(&GetVolume()->Allocator().Lock());
60 		GetVolume()->GetJournal(0)->Unlock(NULL, true);
61 		return B_NO_MEMORY;
62 	}
63 
64 	memset(&Control().stats, 0, sizeof(check_control::stats));
65 
66 	// initialize bitmap
67 	memset(fCheckBitmap, 0, size);
68 	for (off_t block = GetVolume()->ToBlock(GetVolume()->Log())
69 		+ GetVolume()->Log().Length(); block-- > 0;) {
70 		_SetCheckBitmapAt(block);
71 	}
72 
73 	Control().pass = BFS_CHECK_PASS_BITMAP;
74 	Control().stats.block_size = GetVolume()->BlockSize();
75 
76 	// TODO: check reserved area in bitmap!
77 
78 	Start(VISIT_REGULAR | VISIT_INDICES | VISIT_REMOVED
79 		| VISIT_ATTRIBUTE_DIRECTORIES);
80 
81 	return B_OK;
82 }
83 
84 
85 status_t
86 CheckVisitor::WriteBackCheckBitmap()
87 {
88 	if (GetVolume()->IsReadOnly())
89 		return B_OK;
90 
91 	// calculate the number of used blocks in the check bitmap
92 	size_t size = _BitmapSize();
93 	off_t usedBlocks = 0LL;
94 
95 	// TODO: update the allocation groups used blocks info
96 	for (uint32 i = size >> 2; i-- > 0;) {
97 		uint32 compare = 1;
98 		// Count the number of bits set
99 		for (int16 j = 0; j < 32; j++, compare <<= 1) {
100 			if ((compare & fCheckBitmap[i]) != 0)
101 				usedBlocks++;
102 		}
103 	}
104 
105 	Control().stats.freed = GetVolume()->UsedBlocks() - usedBlocks
106 		+ Control().stats.missing;
107 	if (Control().stats.freed < 0)
108 		Control().stats.freed = 0;
109 
110 	// Should we fix errors? Were there any errors we can fix?
111 	if ((Control().flags & BFS_FIX_BITMAP_ERRORS) != 0
112 		&& (Control().stats.freed != 0 || Control().stats.missing != 0)) {
113 		// If so, write the check bitmap back over the original one,
114 		// and use transactions here to play safe - we even use several
115 		// transactions, so that we don't blow the maximum log size
116 		// on large disks, since we don't need to make this atomic.
117 #if 0
118 		// prints the blocks that differ
119 		off_t block = 0;
120 		for (int32 i = 0; i < fNumGroups; i++) {
121 			AllocationBlock cached(fVolume);
122 			for (uint32 j = 0; j < fGroups[i].NumBlocks(); j++) {
123 				cached.SetTo(fGroups[i], j);
124 				for (uint32 k = 0; k < cached.NumBlockBits(); k++) {
125 					if (cached.IsUsed(k) != _CheckBitmapIsUsedAt(block)) {
126 						dprintf("differ block %lld (should be %d)\n", block,
127 							_CheckBitmapIsUsedAt(block));
128 					}
129 					block++;
130 				}
131 			}
132 		}
133 #endif
134 
135 		GetVolume()->SuperBlock().used_blocks
136 			= HOST_ENDIAN_TO_BFS_INT64(usedBlocks);
137 
138 		size_t blockSize = GetVolume()->BlockSize();
139 		off_t numBitmapBlocks = GetVolume()->NumBitmapBlocks();
140 
141 		for (uint32 i = 0; i < numBitmapBlocks; i += 512) {
142 			Transaction transaction(GetVolume(), 1 + i);
143 
144 			uint32 blocksToWrite = 512;
145 			if (blocksToWrite + i > numBitmapBlocks)
146 				blocksToWrite = numBitmapBlocks - i;
147 
148 			status_t status = transaction.WriteBlocks(1 + i,
149 				(uint8*)fCheckBitmap + i * blockSize, blocksToWrite);
150 			if (status < B_OK) {
151 				FATAL(("error writing bitmap: %s\n", strerror(status)));
152 				return status;
153 			}
154 			transaction.Done();
155 		}
156 	}
157 
158 	return B_OK;
159 }
160 
161 
162 status_t
163 CheckVisitor::StartIndexPass()
164 {
165 	// if we don't have indices to rebuild, this pass is done
166 	if (indices.IsEmpty())
167 		return B_ENTRY_NOT_FOUND;
168 
169 	Control().pass = BFS_CHECK_PASS_INDEX;
170 
171 	status_t status = _PrepareIndices();
172 	if (status != B_OK) {
173 		Control().status = status;
174 		return status;
175 	}
176 
177 	Start(VISIT_REGULAR);
178 	return Next();
179 }
180 
181 
182 status_t
183 CheckVisitor::StopChecking()
184 {
185 	if (GetVolume()->IsReadOnly()) {
186 		// We can't fix errors on this volume
187 		Control().flags = 0;
188 	}
189 
190 	if (Control().status != B_ENTRY_NOT_FOUND)
191 		FATAL(("CheckVisitor didn't run through\n"));
192 
193 	_FreeIndices();
194 
195 	recursive_lock_unlock(&GetVolume()->Allocator().Lock());
196 	GetVolume()->GetJournal(0)->Unlock(NULL, true);
197 	return B_OK;
198 }
199 
200 
201 status_t
202 CheckVisitor::VisitDirectoryEntry(Inode* inode, Inode* parent,
203 	const char* treeName)
204 {
205 	Control().inode = inode->ID();
206 	Control().mode = inode->Mode();
207 
208 	if (Pass() != BFS_CHECK_PASS_BITMAP)
209 		return B_OK;
210 
211 	// check if the inode's name is the same as in the b+tree
212 	if (inode->IsRegularNode()) {
213 		RecursiveLocker locker(inode->SmallDataLock());
214 		NodeGetter node(GetVolume());
215 		status_t status = node.SetTo(inode);
216 		if (status != B_OK) {
217 			Control().errors |= BFS_COULD_NOT_OPEN;
218 			Control().status = status;
219 			return B_OK;
220 		}
221 
222 		const char* localName = inode->Name(node.Node());
223 		if (localName == NULL || strcmp(localName, treeName)) {
224 			Control().errors |= BFS_NAMES_DONT_MATCH;
225 			FATAL(("Names differ: tree \"%s\", inode \"%s\"\n", treeName,
226 				localName));
227 
228 			if ((Control().flags & BFS_FIX_NAME_MISMATCHES) != 0) {
229 				// Rename the inode
230 				Transaction transaction(GetVolume(), inode->BlockNumber());
231 
232 				// Note, this may need extra blocks, but the inode will
233 				// only be checked afterwards, so that it won't be lost
234 				status_t status = inode->SetName(transaction, treeName);
235 				if (status == B_OK)
236 					status = inode->WriteBack(transaction);
237 				if (status == B_OK)
238 					status = transaction.Done();
239 				if (status != B_OK) {
240 					Control().status = status;
241 					return B_OK;
242 				}
243 			}
244 		}
245 	}
246 
247 	// Check for the correct mode of the node (if the mode of the
248 	// file don't fit to its parent, there is a serious problem)
249 	if (((parent->Mode() & S_ATTR_DIR) != 0
250 			&& !inode->IsAttribute())
251 		|| ((parent->Mode() & S_INDEX_DIR) != 0
252 			&& !inode->IsIndex())
253 		|| (is_directory(parent->Mode())
254 			&& !inode->IsRegularNode())) {
255 		FATAL(("inode at %" B_PRIdOFF " is of wrong type: %o (parent "
256 			"%o at %" B_PRIdOFF ")!\n", inode->BlockNumber(),
257 			inode->Mode(), parent->Mode(), parent->BlockNumber()));
258 
259 		// if we are allowed to fix errors, we should remove the file
260 		if ((Control().flags & BFS_REMOVE_WRONG_TYPES) != 0
261 			&& (Control().flags & BFS_FIX_BITMAP_ERRORS) != 0) {
262 			Control().status = _RemoveInvalidNode(parent, NULL, inode,
263 				treeName);
264 		} else
265 			Control().status = B_ERROR;
266 
267 		Control().errors |= BFS_WRONG_TYPE;
268 	}
269 	return B_OK;
270 }
271 
272 
273 status_t
274 CheckVisitor::VisitInode(Inode* inode, const char* treeName)
275 {
276 	Control().inode = inode->ID();
277 	Control().mode = inode->Mode();
278 		// (we might have set these in VisitDirectoryEntry already)
279 
280 	// set name
281 	if (treeName == NULL) {
282 		if (inode->GetName(Control().name) < B_OK) {
283 			if (inode->IsContainer())
284 				strcpy(Control().name, "(dir has no name)");
285 			else
286 				strcpy(Control().name, "(node has no name)");
287 		}
288 	} else
289 		strcpy(Control().name, treeName);
290 
291 	status_t status = B_OK;
292 
293 	switch (Pass()) {
294 		case BFS_CHECK_PASS_BITMAP:
295 		{
296 			status = _CheckInodeBlocks(inode, NULL);
297 			if (status != B_OK)
298 				return status;
299 
300 			// Check the B+tree as well
301 			if (inode->IsContainer()) {
302 				bool repairErrors = (Control().flags & BFS_FIX_BPLUSTREES) != 0;
303 				bool errorsFound = false;
304 
305 				status = inode->Tree()->Validate(repairErrors, errorsFound);
306 
307 				if (errorsFound) {
308 					Control().errors |= BFS_INVALID_BPLUSTREE;
309 					if (inode->IsIndex() && treeName != NULL && repairErrors) {
310 						// We completely rebuild corrupt indices
311 						check_index* index = new(std::nothrow) check_index;
312 						if (index == NULL)
313 							return B_NO_MEMORY;
314 
315 						strlcpy(index->name, treeName, sizeof(index->name));
316 						index->run = inode->BlockRun();
317 						Indices().Push(index);
318 					}
319 				}
320 			}
321 
322 			break;
323 		}
324 
325 		case BFS_CHECK_PASS_INDEX:
326 			status = _AddInodeToIndex(inode);
327 			break;
328 	}
329 
330 	Control().status = status;
331 	return B_OK;
332 }
333 
334 
335 status_t
336 CheckVisitor::OpenInodeFailed(status_t reason, ino_t id, Inode* parent,
337 		char* treeName, TreeIterator* iterator)
338 {
339 	FATAL(("Could not open inode at %" B_PRIdOFF ": %s\n", id,
340 		strerror(reason)));
341 
342 	if (treeName != NULL)
343 		strlcpy(Control().name, treeName, B_FILE_NAME_LENGTH);
344 	else
345 		strcpy(Control().name, "(node has no name)");
346 
347 	Control().inode = id;
348 	Control().errors = BFS_COULD_NOT_OPEN;
349 
350 	// TODO: check other error codes; B_IO_ERROR might be a temporary
351 	// issue, so it should be guarded by a force mode
352 	if (reason == B_BAD_VALUE || reason == B_BAD_DATA || reason == B_IO_ERROR) {
353 		// Remove inode from the tree if we can
354 		if (parent != NULL && iterator != NULL
355 			&& (Control().flags & BFS_REMOVE_INVALID) != 0) {
356 			Control().status = _RemoveInvalidNode(parent, iterator->Tree(),
357 				NULL, treeName);
358 		} else
359 			Control().status = B_ERROR;
360 	} else {
361 		Control().status = B_OK;
362 	}
363 
364 	return B_OK;
365 }
366 
367 
368 status_t
369 CheckVisitor::OpenBPlusTreeFailed(Inode* inode)
370 {
371 	FATAL(("Could not open b+tree from inode at %" B_PRIdOFF "\n",
372 		inode->ID()));
373 	return B_OK;
374 }
375 
376 
377 status_t
378 CheckVisitor::TreeIterationFailed(status_t reason, Inode* parent)
379 {
380 	// Iterating over the B+tree failed - we let the checkfs run
381 	// fail completely, as we would delete all files we cannot
382 	// access.
383 	// TODO: maybe have a force parameter that actually does that.
384 	// TODO: we also need to be able to repair broken B+trees!
385 	return reason;
386 }
387 
388 
389 status_t
390 CheckVisitor::_RemoveInvalidNode(Inode* parent, BPlusTree* tree,
391 	Inode* inode, const char* name)
392 {
393 	// It's safe to start a transaction, because Inode::Remove()
394 	// won't touch the block bitmap (which we hold the lock for)
395 	// if we set the INODE_DONT_FREE_SPACE flag - since we fix
396 	// the bitmap anyway.
397 	Transaction transaction(GetVolume(), parent->BlockNumber());
398 	status_t status;
399 
400 	if (inode != NULL) {
401 		inode->Node().flags |= HOST_ENDIAN_TO_BFS_INT32(INODE_DONT_FREE_SPACE);
402 
403 		status = parent->Remove(transaction, name, NULL, false, true);
404 	} else {
405 		parent->WriteLockInTransaction(transaction);
406 
407 		// does the file even exist?
408 		off_t id;
409 		status = tree->Find((uint8*)name, (uint16)strlen(name), &id);
410 		if (status == B_OK)
411 			status = tree->Remove(transaction, name, id);
412 	}
413 
414 	if (status == B_OK) {
415 		entry_cache_remove(GetVolume()->ID(), parent->ID(), name);
416 		transaction.Done();
417 	}
418 
419 	return status;
420 }
421 
422 
423 bool
424 CheckVisitor::_ControlValid()
425 {
426 	if (Control().magic != BFS_IOCTL_CHECK_MAGIC) {
427 		FATAL(("invalid check_control!\n"));
428 		return false;
429 	}
430 
431 	return true;
432 }
433 
434 
435 bool
436 CheckVisitor::_CheckBitmapIsUsedAt(off_t block) const
437 {
438 	size_t size = _BitmapSize();
439 	uint32 index = block / 32;	// 32bit resolution
440 	if (index > size / 4)
441 		return false;
442 
443 	return BFS_ENDIAN_TO_HOST_INT32(fCheckBitmap[index])
444 		& (1UL << (block & 0x1f));
445 }
446 
447 
448 void
449 CheckVisitor::_SetCheckBitmapAt(off_t block)
450 {
451 	size_t size = _BitmapSize();
452 	uint32 index = block / 32;	// 32bit resolution
453 	if (index > size / 4)
454 		return;
455 
456 	fCheckBitmap[index] |= HOST_ENDIAN_TO_BFS_INT32(1UL << (block & 0x1f));
457 }
458 
459 
460 size_t
461 CheckVisitor::_BitmapSize() const
462 {
463 	return GetVolume()->BlockSize() * GetVolume()->NumBitmapBlocks();
464 }
465 
466 
467 status_t
468 CheckVisitor::_CheckInodeBlocks(Inode* inode, const char* name)
469 {
470 	status_t status = _CheckAllocated(inode->BlockRun(), "inode");
471 	if (status != B_OK)
472 		return status;
473 
474 	if (inode->IsSymLink() && (inode->Flags() & INODE_LONG_SYMLINK) == 0) {
475 		// symlinks may not have a valid data stream
476 		if (strlen(inode->Node().short_symlink) >= SHORT_SYMLINK_NAME_LENGTH)
477 			return B_BAD_DATA;
478 
479 		return B_OK;
480 	}
481 
482 	data_stream* data = &inode->Node().data;
483 
484 	// check the direct range
485 
486 	if (data->max_direct_range) {
487 		for (int32 i = 0; i < NUM_DIRECT_BLOCKS; i++) {
488 			if (data->direct[i].IsZero())
489 				break;
490 
491 			status = _CheckAllocated(data->direct[i], "direct");
492 			if (status < B_OK)
493 				return status;
494 
495 			Control().stats.direct_block_runs++;
496 			Control().stats.blocks_in_direct
497 				+= data->direct[i].Length();
498 		}
499 	}
500 
501 	CachedBlock cached(GetVolume());
502 
503 	// check the indirect range
504 
505 	if (data->max_indirect_range) {
506 		status = _CheckAllocated(data->indirect, "indirect");
507 		if (status != B_OK)
508 			return status;
509 
510 		off_t block = GetVolume()->ToBlock(data->indirect);
511 
512 		for (int32 i = 0; i < data->indirect.Length(); i++) {
513 			status = cached.SetTo(block + i);
514 			if (status != B_OK)
515 				RETURN_ERROR(status);
516 
517 			block_run* runs = (block_run*)cached.Block();
518 			int32 runsPerBlock = GetVolume()->BlockSize() / sizeof(block_run);
519 			int32 index = 0;
520 			for (; index < runsPerBlock; index++) {
521 				if (runs[index].IsZero())
522 					break;
523 
524 				status = _CheckAllocated(runs[index], "indirect->run");
525 				if (status < B_OK)
526 					return status;
527 
528 				Control().stats.indirect_block_runs++;
529 				Control().stats.blocks_in_indirect
530 					+= runs[index].Length();
531 			}
532 			Control().stats.indirect_array_blocks++;
533 
534 			if (index < runsPerBlock)
535 				break;
536 		}
537 	}
538 
539 	// check the double indirect range
540 
541 	if (data->max_double_indirect_range) {
542 		status = _CheckAllocated(data->double_indirect, "double indirect");
543 		if (status != B_OK)
544 			return status;
545 
546 		int32 runsPerBlock = runs_per_block(GetVolume()->BlockSize());
547 		int32 runsPerArray = runsPerBlock * data->double_indirect.Length();
548 
549 		CachedBlock cachedDirect(GetVolume());
550 
551 		for (int32 indirectIndex = 0; indirectIndex < runsPerArray;
552 				indirectIndex++) {
553 			// get the indirect array block
554 			status = cached.SetTo(GetVolume()->ToBlock(data->double_indirect)
555 					+ indirectIndex / runsPerBlock);
556 			if (status != B_OK)
557 				return status;
558 
559 			block_run* array = (block_run*)cached.Block();
560 			block_run indirect = array[indirectIndex % runsPerBlock];
561 			// are we finished yet?
562 			if (indirect.IsZero())
563 				return B_OK;
564 
565 			status = _CheckAllocated(indirect, "double indirect->runs");
566 			if (status != B_OK)
567 				return status;
568 
569 			int32 maxIndex
570 				= ((uint32)indirect.Length() << GetVolume()->BlockShift())
571 					/ sizeof(block_run);
572 
573 			for (int32 index = 0; index < maxIndex; ) {
574 				status = cachedDirect.SetTo(GetVolume()->ToBlock(indirect)
575 						+ index / runsPerBlock);
576 				if (status != B_OK)
577 					return status;
578 
579 				block_run* runs = (block_run*)cachedDirect.Block();
580 
581 				do {
582 					// are we finished yet?
583 					if (runs[index % runsPerBlock].IsZero())
584 						return B_OK;
585 
586 					status = _CheckAllocated(runs[index % runsPerBlock],
587 						"double indirect->runs->run");
588 					if (status != B_OK)
589 						return status;
590 
591 					Control().stats.double_indirect_block_runs++;
592 					Control().stats.blocks_in_double_indirect
593 						+= runs[index % runsPerBlock].Length();
594 				} while ((++index % runsPerBlock) != 0);
595 			}
596 
597 			Control().stats.double_indirect_array_blocks++;
598 		}
599 	}
600 
601 	return B_OK;
602 }
603 
604 
605 status_t
606 CheckVisitor::_CheckAllocated(block_run run, const char* type)
607 {
608 	BlockAllocator& allocator = GetVolume()->Allocator();
609 
610 	// make sure the block run is valid
611 	if (!allocator.IsValidBlockRun(run, type)) {
612 		Control().errors |= BFS_INVALID_BLOCK_RUN;
613 		return B_OK;
614 	}
615 
616 	status_t status;
617 
618 	off_t start = GetVolume()->ToBlock(run);
619 	off_t end = start + run.Length();
620 
621 	// check if the run is allocated in the block bitmap on disk
622 	off_t block = start;
623 
624 	while (block < end) {
625 		off_t firstMissing;
626 		status = allocator.CheckBlocks(block, end - block, true, &firstMissing);
627 		if (status == B_OK)
628 			break;
629 		else if (status != B_BAD_DATA)
630 			return status;
631 
632 		off_t afterLastMissing;
633 		status = allocator.CheckBlocks(firstMissing, end - firstMissing, false,
634 			&afterLastMissing);
635 		if (status == B_OK)
636 			afterLastMissing = end;
637 		else if (status != B_BAD_DATA)
638 			return status;
639 
640 		PRINT(("%s: block_run(%" B_PRId32 ", %" B_PRIu16 ", %" B_PRIu16 ")"
641 			": blocks %" B_PRIdOFF " - %" B_PRIdOFF " are not allocated!\n",
642 			type, run.AllocationGroup(), run.Start(),
643 			run.Length(), firstMissing, afterLastMissing - 1));
644 
645 		Control().stats.missing += afterLastMissing - firstMissing;
646 
647 		block = afterLastMissing;
648 	}
649 
650 	// set bits in check bitmap, while checking if they're already set
651 	off_t firstSet = -1;
652 
653 	for (block = start; block < end; block++) {
654 		if (_CheckBitmapIsUsedAt(block)) {
655 			if (firstSet == -1) {
656 				firstSet = block;
657 				Control().errors |= BFS_BLOCKS_ALREADY_SET;
658 			}
659 			Control().stats.already_set++;
660 		} else {
661 			if (firstSet != -1) {
662 				FATAL(("%s: block_run(%d, %u, %u): blocks %" B_PRIdOFF
663 					" - %" B_PRIdOFF " are already set!\n", type,
664 					(int)run.AllocationGroup(), run.Start(), run.Length(),
665 					firstSet, block - 1));
666 				firstSet = -1;
667 			}
668 			_SetCheckBitmapAt(block);
669 		}
670 	}
671 
672 	return B_OK;
673 }
674 
675 
676 status_t
677 CheckVisitor::_PrepareIndices()
678 {
679 	int32 count = 0;
680 
681 	for (int32 i = 0; i < Indices().CountItems(); i++) {
682 		check_index* index = Indices().Array()[i];
683 		Vnode vnode(GetVolume(), index->run);
684 		Inode* inode;
685 		status_t status = vnode.Get(&inode);
686 		if (status != B_OK) {
687 			FATAL(("check: Could not open index at %" B_PRIdOFF "\n",
688 				GetVolume()->ToBlock(index->run)));
689 			return status;
690 		}
691 
692 		BPlusTree* tree = inode->Tree();
693 		if (tree == NULL) {
694 			// TODO: We can't yet repair those
695 			continue;
696 		}
697 
698 		status = tree->MakeEmpty();
699 		if (status != B_OK)
700 			return status;
701 
702 		index->inode = inode;
703 		vnode.Keep();
704 		count++;
705 	}
706 
707 	return count == 0 ? B_ENTRY_NOT_FOUND : B_OK;
708 }
709 
710 
711 void
712 CheckVisitor::_FreeIndices()
713 {
714 	for (int32 i = 0; i < Indices().CountItems(); i++) {
715 		check_index* index = Indices().Array()[i];
716 		if (index->inode != NULL) {
717 			put_vnode(GetVolume()->FSVolume(),
718 				GetVolume()->ToVnode(index->inode->BlockRun()));
719 		}
720 		delete index;
721 	}
722 	Indices().MakeEmpty();
723 }
724 
725 
726 status_t
727 CheckVisitor::_AddInodeToIndex(Inode* inode)
728 {
729 	Transaction transaction(GetVolume(), inode->BlockNumber());
730 
731 	for (int32 i = 0; i < Indices().CountItems(); i++) {
732 		check_index* index = Indices().Array()[i];
733 		if (index->inode == NULL)
734 			continue;
735 
736 		index->inode->WriteLockInTransaction(transaction);
737 
738 		BPlusTree* tree = index->inode->Tree();
739 		if (tree == NULL)
740 			return B_ERROR;
741 
742 		status_t status = B_OK;
743 
744 		if (!strcmp(index->name, "name")) {
745 			if (inode->InNameIndex()) {
746 				char name[B_FILE_NAME_LENGTH];
747 				if (inode->GetName(name, B_FILE_NAME_LENGTH) != B_OK)
748 					return B_ERROR;
749 
750 				status = tree->Insert(transaction, name, inode->ID());
751 			}
752 		} else if (!strcmp(index->name, "last_modified")) {
753 			if (inode->InLastModifiedIndex()) {
754 				status = tree->Insert(transaction, inode->OldLastModified(),
755 					inode->ID());
756 			}
757 		} else if (!strcmp(index->name, "size")) {
758 			if (inode->InSizeIndex())
759 				status = tree->Insert(transaction, inode->Size(), inode->ID());
760 		} else {
761 			uint8 key[MAX_INDEX_KEY_LENGTH];
762 			size_t keyLength = sizeof(key);
763 			if (inode->ReadAttribute(index->name, B_ANY_TYPE, 0, key,
764 					&keyLength) == B_OK) {
765 				status = tree->Insert(transaction, key, keyLength, inode->ID());
766 			}
767 		}
768 
769 		if (status != B_OK)
770 			return status;
771 	}
772 
773 	return transaction.Done();
774 }
775