xref: /haiku/src/bin/bfs_tools/recover.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
1 /*
2  * Copyright (c) 2001-2008 pinc Software. All Rights Reserved.
3  * Released under the terms of the MIT license.
4  */
5 
6 //!	recovers corrupt BFS disks
7 
8 
9 #include <set>
10 
11 #include "Disk.h"
12 #include "Inode.h"
13 #include "Hashtable.h"
14 #include "BPlusTree.h"
15 #include "dump.h"
16 
17 #include <String.h>
18 #include <fs_info.h>
19 
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <unistd.h>
24 #include <ctype.h>
25 
26 
27 extern const char *__progname;
28 static const char *kProgramName = __progname;
29 
30 bool gCreateIndices = false;
31 bool gDumpMissingInodes = false;
32 bool gRawMode = false;
33 bool gVerbose = false;
34 
35 
36 // TODO: add a cache for all inodes
37 typedef std::set<block_run> RunSet;
38 
39 
40 class InodeHashtable {
41 	public:
42 		InodeHashtable(int capacity)
43 			:
44 			fHashtable(capacity),
45 			fLastChecked(0)
46 		{
47 			fHashtable.SetHashFunction((uint32 (*)(const void *))BlockRunHash);
48 			fHashtable.SetCompareFunction((bool (*)(const void *, const void *))
49 				BlockRunCompare);
50 		}
51 
52 		Inode* Acquire(Inode* inode)
53 		{
54 			if (inode == NULL)
55 				return NULL;
56 
57 			status_t status = inode->AcquireBuffer();
58 			if (status != B_OK) {
59 				fprintf(stderr, "Could not retrieve buffer for inode %"
60 					B_PRIdOFF ": %s\n", inode->Offset(), strerror(status));
61 				return NULL;
62 			}
63 			return inode;
64 		}
65 
66 		void Release(Inode* inode)
67 		{
68 			inode->ReleaseBuffer();
69 		}
70 
71 		Inode* Get(block_run run)
72 		{
73 			return Acquire((Inode *)fHashtable.GetValue(&run));
74 		}
75 
76 		bool Insert(Inode* inode)
77 		{
78 			bool success = fHashtable.Put(&inode->BlockRun(), inode);
79 			if (success)
80 				inode->ReleaseBuffer();
81 
82 			return success;
83 		}
84 
85 		bool Contains(block_run *key)
86 		{
87 			return fHashtable.ContainsKey(key);
88 		}
89 
90 		Inode* Remove(block_run *key)
91 		{
92 			return Acquire((Inode*)fHashtable.Remove(key));
93 		}
94 
95 		status_t GetNextEntry(Inode **_inode)
96 		{
97 			status_t status = fHashtable.GetNextEntry((void**)_inode);
98 			if (status == B_OK) {
99 				if (Acquire(*_inode) == NULL)
100 					return B_NO_MEMORY;
101 			}
102 
103 			return status;
104 		}
105 
106 		void Rewind()
107 		{
108 			fHashtable.Rewind();
109 		}
110 
111 		bool IsEmpty() const
112 		{
113 			return fHashtable.IsEmpty();
114 		}
115 
116 		void MakeEmpty()
117 		{
118 			fHashtable.MakeEmpty(HASH_EMPTY_NONE, HASH_EMPTY_DELETE);
119 		}
120 
121 		static uint32 BlockRunHash(const block_run *run)
122 		{
123 			return (run->allocation_group << 16) | run->start;
124 		}
125 
126 		static bool BlockRunCompare(const block_run *runA, const block_run *runB)
127 		{
128 			return *runA == *runB;
129 		}
130 
131 	private:
132 		Hashtable	fHashtable;
133 		bigtime_t	fLastChecked;
134 		uint32		fPercentUsed;
135 };
136 
137 
138 class InodeGetter {
139 	public:
140 		InodeGetter(Disk& disk, block_run run)
141 		{
142 			fInode = Inode::Factory(&disk, run);
143 			if (fInode != NULL)
144 				fInode->AcquireBuffer();
145 		}
146 
147 		~InodeGetter()
148 		{
149 			if (fInode != NULL)
150 				fInode->ReleaseBuffer();
151 		}
152 
153 		Inode* Node() const
154 		{
155 			return fInode;
156 		}
157 
158 		void Detach()
159 		{
160 			fInode = NULL;
161 		}
162 
163 	private:
164 		Inode*	fInode;
165 };
166 
167 
168 RunSet gMainInodes;
169 	// contains all inodes found on disk in the general data area
170 InodeHashtable gLogged(50);
171 	// contains all inodes found in the log area
172 InodeHashtable gMissing(50);
173 InodeHashtable gMissingEmpty(25);
174 
175 
176 class HashtableInodeSource : public Inode::Source {
177 public:
178 	HashtableInodeSource(Disk& disk)
179 		:
180 		fDisk(disk)
181 	{
182 	}
183 
184 	virtual Inode *InodeAt(block_run run)
185 	{
186 		Inode *inode;
187 		if ((inode = gLogged.Get(run)) != NULL)
188 			return Inode::Factory(&fDisk, inode, false);
189 
190 		if ((inode = gMissing.Get(run)) != NULL)
191 			return Inode::Factory(&fDisk, inode, false);
192 
193 		if (gMainInodes.find(run) == gMainInodes.end())
194 			return NULL;
195 
196 		return Inode::Factory(&fDisk, run);
197 	}
198 
199 private:
200 	Disk&	fDisk;
201 };
202 
203 
204 bool
205 operator<(const block_run& a, const block_run& b)
206 {
207 	return a.allocation_group < b.allocation_group
208 		|| (a.allocation_group == b.allocation_group && a.start < b.start);
209 }
210 
211 
212 void
213 collectInodes(Disk& disk, RunSet* set, InodeHashtable* hashTable, off_t start,
214 	off_t end)
215 {
216 	char buffer[8192];
217 	Inode inode(&disk, (bfs_inode *)buffer, false);
218 
219 	off_t directories = 0LL;
220 	off_t directorySize = 0LL;
221 	off_t files = 0LL;
222 	off_t fileSize = 0LL;
223 	off_t symlinks = 0LL;
224 	off_t count = 0LL;
225 
226 	off_t position = start;
227 	bigtime_t lastUpdate = system_time();
228 
229 	for (off_t offset = start; offset < end; offset += sizeof(buffer)) {
230 		if (disk.ReadAt(offset, buffer, sizeof(buffer)) < B_OK) {
231 			fprintf(stderr, "could not read from device!\n");
232 			break;
233 		}
234 
235 		//if ((offset % (disk.BlockSize() << disk.SuperBlock()->ag_shift)) == 0)
236 		//	printf("reading block %lld, allocation group %lld, %lld inodes...\33[1A\n", offset / disk.BlockSize(),offset / (disk.BlockSize() << disk.SuperBlock()->ag_shift), count);
237 
238 		for (uint32 i = 0; i < sizeof(buffer); i += disk.BlockSize()) {
239 			inode.SetTo((bfs_inode *)(buffer + i));
240 			if (inode.InitCheck() == B_OK) {
241 				if (inode.Flags() & INODE_DELETED)
242 					continue;
243 
244 				Inode *node = Inode::Factory(&disk, &inode);
245 				if (node != NULL) {
246 					if (gVerbose)
247 						printf("  node: %" B_PRIdOFF " \"%s\"\n", position,
248 							node->Name());
249 
250 					if (set != NULL)
251 						set->insert(node->BlockRun());
252 					else
253 						hashTable->Insert(node);
254 
255 					if (node->IsDirectory()) {
256 						directories++;
257 						directorySize += node->Size();
258 					} else if (node->IsFile()) {
259 						files++;
260 						fileSize += node->Size();
261 					} else if (node->IsSymlink()) {
262 						symlinks++;
263 					}
264 					count++;
265 				} else if (gVerbose) {
266 					printf("\nunrecognized inode:");
267 					dump_inode(&inode, inode.InodeBuffer());
268 				}
269 			}
270 			position += disk.BlockSize();
271 		}
272 		if (system_time() - lastUpdate > 500000) {
273 			printf("  block %" B_PRIdOFF " (%" B_PRIdOFF "%%), %" B_PRIdOFF
274 				" inodes\33[1A\n", offset,
275 				100 * (offset - start) / (end - start), count);
276 			lastUpdate = system_time();
277 		}
278 	}
279 	printf("\n%" B_PRIdOFF " inodes found.\n", count);
280 
281 	printf("\n%20" B_PRIdOFF " directories found (total of %" B_PRIdOFF
282 		" bytes)\n%20" B_PRIdOFF " files found (total of %" B_PRIdOFF
283 		" bytes)\n%20" B_PRIdOFF " symlinks found\n"
284 		"--------------------\n"
285 		"%20" B_PRIdOFF " inodes total found.\n",
286 		directories, directorySize, files, fileSize, symlinks, count);
287 }
288 
289 
290 void
291 collectLogInodes(Disk &disk)
292 {
293 	// scan log area
294 	off_t offset = disk.ToOffset(disk.Log());
295 	off_t end = offset + (disk.Log().length << disk.BlockShift());
296 
297 	printf("\nsearching from %" B_PRIdOFF " to %" B_PRIdOFF " (log area)\n",
298 		offset, end);
299 
300 	collectInodes(disk, NULL, &gLogged, offset, end);
301 }
302 
303 
304 void
305 collectRealInodes(Disk &disk)
306 {
307 	// first block after bootblock, bitmap, and log
308 	off_t offset = disk.ToOffset(disk.Log()) + (disk.Log().length
309 		<< disk.BlockShift());
310 	off_t end = (off_t)disk.NumBlocks() << disk.BlockShift();
311 
312 	printf("\nsearching from %" B_PRIdOFF " to %" B_PRIdOFF " (main area)\n",
313 		offset, end);
314 
315 	collectInodes(disk, &gMainInodes, NULL, offset, end);
316 }
317 
318 
319 Directory *
320 getNameIndex(Disk &disk)
321 {
322 	InodeGetter getter(disk, disk.Indices());
323 	Directory *indices = dynamic_cast<Directory *>(getter.Node());
324 
325 	block_run run;
326 	if (indices != NULL && indices->FindEntry("name", &run) == B_OK) {
327 		InodeGetter getter(disk, run);
328 		Inode* node = getter.Node();
329 		getter.Detach();
330 		return dynamic_cast<Directory *>(node);
331 	}
332 
333 	// search name index
334 
335 	RunSet::iterator iterator = gMainInodes.begin();
336 	for (; iterator != gMainInodes.end(); iterator++) {
337 		InodeGetter getter(disk, *iterator);
338 		Inode* node = getter.Node();
339 
340 		if (!node || !node->IsIndex() || node->Name() == NULL)
341 			continue;
342 		if (!strcmp(node->Name(), "name") && node->Mode() & S_STR_INDEX)
343 			return dynamic_cast<Directory *>(node);
344 	}
345 
346 	return NULL;
347 }
348 
349 
350 void
351 checkDirectoryContents(Disk& disk, Directory *dir)
352 {
353 	dir->Rewind();
354 
355 	char name[B_FILE_NAME_LENGTH];
356 	block_run run;
357 
358 	while (dir->GetNextEntry(name, &run) == B_OK) {
359 		if (run == dir->BlockRun() || run == dir->Parent()
360 			|| gMainInodes.find(run) != gMainInodes.end())
361 			continue;
362 
363 		Inode *missing = gMissing.Get(run);
364 		if (missing != NULL) {
365 			if (missing->SetName(name) < B_OK) {
366 				fprintf(stderr, "setting name of missing node to "
367 					"\"%s\" failed!", name);
368 			}
369 			if (gVerbose) {
370 				printf("Set name of missing node (%" B_PRId32
371 					", %d) to \"%s\" (%s)\n",
372 					run.allocation_group, run.start, missing->Name(), name);
373 			}
374 
375 			missing->SetParent(dir->BlockRun());
376 		}
377 //		if (node->Mode() & S_INDEX_DIR)
378 //		{
379 //			if (node->Mode() & S_STR_INDEX)
380 //				printf("index directory (%ld, %d): \"%s\" is missing (%ld, %d, %d)\n",node->BlockRun().allocation_group,node->BlockRun().start,name,run.allocation_group,run.start,run.length);
381 //			else
382 //				printf("index directory (%ld, %d): key is missing (%ld, %d, %d)\n",node->BlockRun().allocation_group,node->BlockRun().start,run.allocation_group,run.start,run.length);
383 //		}
384 		else {
385 			// missing inode has not been found
386 			if (gVerbose) {
387 				printf("directory \"%s\" (%" B_PRId32 ", %d): node \"%s\" is "
388 					"missing (%" B_PRId32 ", %d, %d)\n", dir->Name(),
389 					dir->BlockRun().allocation_group,
390 					dir->BlockRun().start, name,
391 					run.allocation_group, run.start, run.length);
392 			}
393 
394 			if ((missing = (Inode *)gLogged.Remove(&run)) != NULL) {
395 				// missing inode is in the log
396 				if (gVerbose)
397 					printf("found missing entry in log!\n");
398 				if (missing->InodeBuffer()->parent != dir->BlockRun()) {
399 					if (gVerbose)
400 						puts("\tparent directories differ (may be an old "
401 							"version of it), reseting parent.");
402 					missing->SetParent(dir->BlockRun());
403 				}
404 				if (!gMissing.Insert(missing))
405 					delete missing;
406 			} else if (!gMissingEmpty.Contains(&run)) {
407 				// not known at all yet
408 				Inode *empty = Inode::EmptyInode(&disk, name, 0);
409 				if (empty) {
410 					empty->SetBlockRun(run);
411 					empty->SetParent(dir->BlockRun());
412 					if (gVerbose)
413 						printf("\tname = %s\n", empty->Name());
414 
415 					if (!gMissingEmpty.Insert(empty))
416 						delete empty;
417 				}
418 			}
419 		}
420 	}
421 }
422 
423 
424 void
425 checkStructure(Disk &disk)
426 {
427 	off_t count = 0;
428 	Inode* node;
429 
430 	RunSet::iterator iterator = gMainInodes.begin();
431 	for (; iterator != gMainInodes.end(); iterator++) {
432 		InodeGetter getter(disk, *iterator);
433 		node = getter.Node();
434 
435 		count++;
436 		if ((count % 50) == 0)
437 			fprintf(stderr, "%" B_PRIdOFF " inodes processed...\33[1A\n", count);
438 
439 		if (node == NULL)
440 			continue;
441 
442 		if (node->IsDirectory() && !node->IsIndex()) {
443 			// check if all entries are in the hashtable
444 			Directory* directory = dynamic_cast<Directory*>(node);
445 			if (directory != NULL)
446 				checkDirectoryContents(disk, directory);
447 			else {
448 				printf("Node \"%s\" at %" B_PRId32
449 					",%d looks like a directory, but isn't.\n",
450 					node->Name(), node->BlockRun().allocation_group,
451 					node->BlockRun().start);
452 			}
453 		}
454 
455 		// check for the parent directory
456 
457 		block_run run = node->Parent();
458 		InodeGetter parentGetter(disk, run);
459 		Inode *parentNode = parentGetter.Node();
460 
461 		Directory *dir = dynamic_cast<Directory *>(parentNode);
462 		if (dir || (parentNode && (node->Mode() & S_ATTR_DIR))) {
463 			// entry has a valid parent directory, so it's assumed to be a valid entry
464 			disk.BlockBitmap()->BackupSet(node, true);
465 		} else if (node->Mode() & S_ATTR) {
466 			if (gVerbose) {
467 				printf("attribute \"%s\" at %" B_PRId32 ",%d misses its parent.\n",
468 					node->Name(), node->BlockRun().allocation_group,
469 					node->BlockRun().start);
470 				puts("\thandling not yet implemented...");
471 			}
472 		} else /*if ((node->Flags() & INODE_DELETED) == 0)*/ {
473 			Inode* missing = gMissing.Get(run);
474 			dir = dynamic_cast<Directory *>(missing);
475 
476 			if (missing == NULL) {
477 				if (gVerbose) {
478 					printf("%s \"%s\" (%" B_PRId32 ", %d, mode = %10" B_PRIo32
479 						"): parent directory is missing (%" B_PRId32
480 						", %d, %d), may be deleted!\n",
481 						node->IsFile() ? "file" : "node", node->Name(),
482 						node->BlockRun().allocation_group,
483 						node->BlockRun().start,
484 						node->Mode(), run.allocation_group, run.start,
485 						run.length);
486 				}
487 
488 				if ((dir = dynamic_cast<Directory *>((Inode *)gLogged.Remove(
489 						&run))) != NULL) {
490 					if (gVerbose) {
491 						printf("found directory \"%s\" in log:\n", dir->Name());
492 						if (dir->Size() > 0)
493 							dump_inode(dir, dir->InodeBuffer());
494 						else
495 							puts("\tempty inode.");
496 					}
497 				} else {
498 					if (gVerbose)
499 						puts("\tcreate parent missing entry");
500 
501 					Inode *nameNode = (Inode *)gMissingEmpty.Remove(&run);
502 					if (nameNode != NULL) {
503 						nameNode->SetMode(S_IFDIR);
504 						if (gVerbose)
505 							printf("found missing name!\n");
506 					} else {
507 						BString parentName;
508 						parentName << "__directory " << run.allocation_group
509 							<< ":" << (int32)run.start;
510 
511 						nameNode = Inode::EmptyInode(&disk, parentName.String(),
512 							S_IFDIR);
513 						if (nameNode) {
514 							nameNode->SetBlockRun(run);
515 							nameNode->SetParent(disk.Root());
516 						}
517 					}
518 
519 					if (nameNode) {
520 						dir = new Directory(*nameNode);
521 						if (dir->CopyBuffer() < B_OK)
522 							puts("could not copy buffer!");
523 						else
524 							delete nameNode;
525 					}
526 				}
527 				if (dir) {
528 					dir->AcquireBuffer();
529 
530 					if (!gMissing.Insert(dir)) {
531 						printf("could not add dir!!\n");
532 						delete dir;
533 						dir = NULL;
534 					}
535 				}
536 			} else if (missing != NULL && dir == NULL && gVerbose) {
537 				printf("%s \"%s\" (%" B_PRId32 ", %d, mode = %10" B_PRIo32
538 					"): parent directory found in missing list (%" B_PRId32
539 					", %d, %d), but it's not a dir!\n",
540 					node->IsFile() ? "file" : "node", node->Name(),
541 					node->BlockRun().allocation_group, node->BlockRun().start,
542 					node->Mode(), run.allocation_group, run.start, run.length);
543 			} else if (gVerbose) {
544 				printf("%s \"%s\" (%" B_PRId32 ", %d, mode = %10" B_PRIo32
545 					"): parent directory found in missing list (%" B_PRId32
546 					", %d, %d)!\n",
547 					node->IsFile() ? "file" : "node", node->Name(),
548 					node->BlockRun().allocation_group, node->BlockRun().start,
549 					node->Mode(), run.allocation_group, run.start, run.length);
550 			}
551 
552 			if (dir) {
553 				dir->AddEntry(node);
554 				dir->ReleaseBuffer();
555 			}
556 		}
557 //			else
558 //			{
559 //				printf("node %s\n", node->Name());
560 //				status_t status = dir->Contains(node);
561 //				if (status == B_ENTRY_NOT_FOUND)
562 //					printf("node \"%s\": parent directory \"%s\" contains no link to this node!\n",node->Name(),dir->Name());
563 //				else if (status != B_OK)
564 //					printf("node \"%s\": parent directory \"%s\" error: %s\n",node->Name(),dir->Name(),strerror(status));
565 //			}
566 
567 		// check for attributes
568 
569 		run = node->Attributes();
570 		if (!run.IsZero()) {
571 			//printf("node \"%s\" (%ld, %d, mode = %010lo): has attribute dir!\n",node->Name(),node->BlockRun().allocation_group,node->BlockRun().start,node->Mode());
572 
573 			if (gMainInodes.find(run) == gMainInodes.end()) {
574 				if (gVerbose) {
575 					printf("node \"%s\": attributes are missing (%" B_PRId32
576 						", %d, %d)\n", node->Name(), run.allocation_group,
577 						run.start, run.length);
578 				}
579 
580 				if ((dir = (Directory *)gMissing.Get(run)) != NULL) {
581 					if (gVerbose)
582 						puts("\tfound in missing");
583 					dir->SetMode(dir->Mode() | S_ATTR_DIR);
584 					dir->SetParent(node->BlockRun());
585 				} else {
586 					if (gVerbose)
587 						puts("\tcreate new!");
588 
589 					Inode *empty = Inode::EmptyInode(&disk, NULL,
590 						S_IFDIR | S_ATTR_DIR);
591 					if (empty) {
592 						empty->SetBlockRun(run);
593 						empty->SetParent(node->BlockRun());
594 
595 						dir = new Directory(*empty);
596 						if (dir->CopyBuffer() < B_OK)
597 							puts("could not copy buffer!");
598 						else
599 							delete empty;
600 
601 						if (!gMissing.Insert(dir)) {
602 							puts("\tcould not add attribute dir");
603 							delete dir;
604 						}
605 					}
606 				}
607 			}
608 		}
609 	}
610 	printf("%" B_PRIdOFF " inodes processed.\n", count);
611 
612 	Directory *directory = getNameIndex(disk);
613 	if (directory != NULL) {
614 		puts("\n*** Search names of missing inodes in the name index");
615 
616 		BPlusTree *tree;
617 		if (directory->GetTree(&tree) == B_OK && tree->Validate(gVerbose) == B_OK) {
618 			char name[B_FILE_NAME_LENGTH];
619 			block_run run;
620 			directory->Rewind();
621 			while (directory->GetNextEntry(name, &run) >= B_OK) {
622 				if ((node = gMissing.Get(run)) == NULL)
623 					continue;
624 
625 				if (gVerbose) {
626 					printf("  Node found: %" B_PRId32 ":%d\n",
627 						run.allocation_group, run.start);
628 				}
629 				if (node->Name() == NULL || strcmp(node->Name(), name)) {
630 					if (gVerbose) {
631 						printf("\tnames differ: %s -> %s (indices)\n",
632 							node->Name(), name);
633 					}
634 					node->SetName(name);
635 				}
636 			}
637 		} else
638 			printf("\tname index is corrupt!\n");
639 
640 		directory->ReleaseBuffer();
641 	} else
642 		printf("*** Name index corrupt or not existent!\n");
643 
644 	if (!gVerbose)
645 		return;
646 
647 	if (!gMissing.IsEmpty())
648 		puts("\n*** Missing inodes:");
649 
650 	gMissing.Rewind();
651 	while (gMissing.GetNextEntry(&node) == B_OK) {
652 		if (gDumpMissingInodes)
653 			dump_inode(node, node->InodeBuffer());
654 
655 		Directory *dir = dynamic_cast<Directory *>(node);
656 		if (dir) {
657 			printf("\ndirectory \"%s\" (%" B_PRId32 ", %d) contents:\n",
658 				node->Name(), node->BlockRun().allocation_group,
659 				node->BlockRun().start);
660 
661 			dir->Rewind();
662 
663 			char name[B_FILE_NAME_LENGTH];
664 			block_run run;
665 			while (dir->GetNextEntry(name, &run) == B_OK) {
666 				printf("\t\"%s\" (%" B_PRId32 ", %d, %d)\n", name,
667 					run.allocation_group, run.start, run.length);
668 			}
669 
670 			BPlusTree *tree;
671 			if (dir->GetTree(&tree) < B_OK)
672 				continue;
673 
674 			uint16 length;
675 			off_t offset;
676 
677 			while (tree->GetNextEntry(name, &length, B_FILE_NAME_LENGTH,
678 					&offset) == B_OK) {
679 				name[length] = 0;
680 
681 				run = disk.ToBlockRun(offset);
682 				printf("%s: block_run == (%5" B_PRId32 ",%5d,%5d), \"%s\"\n",
683 					dir->Name(), run.allocation_group, run.start, run.length,
684 					name);
685 			}
686 
687 			//tree->WriteTo(dir);
688 			//disk.WriteAt(dir->Offset(),dir->InodeBuffer(),disk.BlockSize());
689 		}
690 
691 		gMissing.Release(node);
692 	}
693 }
694 
695 
696 void
697 copyInodes(Disk& disk, const char* copyTo)
698 {
699 	if (copyTo == NULL)
700 		return;
701 
702 	HashtableInodeSource source(disk);
703 	Inode *node;
704 
705 	int32 count = 0;
706 
707 	RunSet::iterator iterator = gMainInodes.begin();
708 	for (; iterator != gMainInodes.end(); iterator++) {
709 		InodeGetter getter(disk, *iterator);
710 		Inode* node = getter.Node();
711 
712 		if (node && !node->IsIndex() && !node->IsAttributeDirectory())
713 			node->CopyTo(copyTo, true, &source);
714 
715 		if ((++count % 500) == 0)
716 			fprintf(stderr, "copied %" B_PRId32 " files...\n", count);
717 	}
718 
719 	gMissing.Rewind();
720 	while (gMissing.GetNextEntry(&node) == B_OK) {
721 		if (!node->IsIndex() && !node->IsAttributeDirectory())
722 			node->CopyTo(copyTo, true, &source);
723 
724 		gMissing.Release(node);
725 	}
726 }
727 
728 
729 void
730 usage(char *name)
731 {
732 	fprintf(stderr,"usage: %s [-idv] [-r [start-offset] [end-offset]] <device> [recover-to-path]\n"
733 		"\t-i\trecreate indices on target disk\n"
734 		"\t-d\tdump missing and recreated i-nodes\n"
735 		"\t-r\tdisk access in raw mode (use only if the partition table is invalid)\n"
736 		"\t-s\trecreate superblock and exit (for experts only, don't use this\n"
737 		"\t\tif you don't know what you're doing)\n"
738 		"\t-v\tverbose output\n", name);
739 	exit(-1);
740 }
741 
742 
743 int
744 main(int argc, char **argv)
745 {
746 	char *fileName = strrchr(argv[0], '/');
747 	fileName = fileName ? fileName + 1 : argv[0];
748 	bool recreateSuperBlockOnly = false;
749 
750 	off_t startOffset = 0, endOffset = -1;
751 
752 	puts("Copyright (c) 2001-2008 pinc Software.");
753 
754 	if (argc < 2 || !strcmp(argv[1], "--help"))
755 		usage(fileName);
756 
757 	while (*++argv) {
758 		char *arg = *argv;
759 		if (*arg == '-') {
760 			while (*++arg && isalpha(*arg)) {
761 				switch (arg[0]) {
762 					case 'r':
763 					{
764 						gRawMode = true;
765 
766 						if (argv[1] && isdigit((argv[1])[0])) {
767 							argv++;
768 							arg = *argv;
769 
770 							startOffset = atoll(arg);
771 						}
772 						if (argv[1] && isdigit((argv[1])[0])) {
773 							argv++;
774 							arg = *argv;
775 
776 							endOffset = atoll(arg);
777 						}
778 						if (endOffset != -1 && endOffset < startOffset)
779 							usage(fileName);
780 						break;
781 					}
782 					case 'v':
783 						gVerbose = true;
784 						break;
785 					case 'i':
786 						gCreateIndices = true;
787 						break;
788 					case 'd':
789 						gDumpMissingInodes = true;
790 						break;
791 					case 's':
792 						recreateSuperBlockOnly = true;
793 						break;
794 				}
795 			}
796 		} else
797 			break;
798 	}
799 
800 	Disk disk(argv[0], gRawMode, startOffset, endOffset);
801 	if (disk.InitCheck() < B_OK) {
802 		fprintf(stderr,"Could not open device or file: %s\n",
803 			strerror(disk.InitCheck()));
804 		return -1;
805 	}
806 
807 	if (argv[1] != NULL) {
808 		dev_t device = dev_for_path(argv[1]);
809 		fs_info info;
810 		if (fs_stat_dev(device, &info) == B_OK) {
811 			if (!strcmp(info.device_name, disk.Path().Path())) {
812 				fprintf(stderr,"The source and target device are identical, "
813 					"you currently need\n"
814 					"to have another disk to recover to, sorry!\n");
815 				return -1;
816 			}
817 			if ((info.flags & (B_FS_IS_PERSISTENT | B_FS_HAS_ATTR))
818 					!= (B_FS_IS_PERSISTENT | B_FS_HAS_ATTR)) {
819 				fprintf(stderr, "%s: The target file system (%s) does not have "
820 					"the required\n"
821 					"\tfunctionality in order to restore all information.\n",
822 					kProgramName, info.fsh_name);
823 				return -1;
824 			}
825 		}
826 	}
827 
828 	bool recreatedSuperBlock = false;
829 
830 	if (disk.ValidateSuperBlock() < B_OK) {
831 		fprintf(stderr, "The disk's superblock is corrupt!\n");
832 		if (disk.RecreateSuperBlock() < B_OK) {
833 			fprintf(stderr, "Can't recreate the disk's superblock, sorry!\n");
834 			return -1;
835 		}
836 		recreatedSuperBlock = true;
837 	}
838 	if (gVerbose) {
839 		puts("\n*** The superblock:\n");
840 		dump_super_block(disk.SuperBlock());
841 	}
842 
843 	if (recreateSuperBlockOnly) {
844 		if (!recreatedSuperBlock) {
845 			printf("Superblock was valid, no changes made.\n");
846 			return 0;
847 		}
848 
849 		status_t status = disk.WriteAt(512, disk.SuperBlock(),
850 			sizeof(disk_super_block));
851 		if (status < B_OK) {
852 			fprintf(stderr, "Could not write superblock: %s!\n",
853 				strerror(status));
854 			return 1;
855 		}
856 		return 0;
857 	}
858 
859 	puts("\n*** Collecting inodes...");
860 
861 	collectLogInodes(disk);
862 	collectRealInodes(disk);
863 
864 	puts("\n*** Checking Disk Structure Integrity...");
865 
866 	checkStructure(disk);
867 
868 	if (argv[1])
869 		copyInodes(disk, argv[1]);
870 
871 	//disk.WriteBootBlock();
872 	//disk.BlockBitmap()->CompareWithBackup();
873 
874 	gMissing.MakeEmpty();
875 	gLogged.MakeEmpty();
876 
877 	return 0;
878 }
879 
880