/* * Copyright (c) 2001-2008 pinc Software. All Rights Reserved. * Released under the terms of the MIT license. */ //! recovers corrupt BFS disks #include #include "Disk.h" #include "Inode.h" #include "Hashtable.h" #include "BPlusTree.h" #include "dump.h" #include #include #include #include #include #include #include extern const char *__progname; static const char *kProgramName = __progname; bool gCreateIndices = false; bool gDumpMissingInodes = false; bool gRawMode = false; bool gVerbose = false; // TODO: add a cache for all inodes typedef std::set RunSet; class InodeHashtable { public: InodeHashtable(int capacity) : fHashtable(capacity), fLastChecked(0) { fHashtable.SetHashFunction((uint32 (*)(const void *))BlockRunHash); fHashtable.SetCompareFunction((bool (*)(const void *, const void *)) BlockRunCompare); } Inode* Acquire(Inode* inode) { if (inode == NULL) return NULL; status_t status = inode->AcquireBuffer(); if (status != B_OK) { fprintf(stderr, "Could not retrieve buffer for inode %" B_PRIdOFF ": %s\n", inode->Offset(), strerror(status)); return NULL; } return inode; } void Release(Inode* inode) { inode->ReleaseBuffer(); } Inode* Get(block_run run) { return Acquire((Inode *)fHashtable.GetValue(&run)); } bool Insert(Inode* inode) { bool success = fHashtable.Put(&inode->BlockRun(), inode); if (success) inode->ReleaseBuffer(); return success; } bool Contains(block_run *key) { return fHashtable.ContainsKey(key); } Inode* Remove(block_run *key) { return Acquire((Inode*)fHashtable.Remove(key)); } status_t GetNextEntry(Inode **_inode) { status_t status = fHashtable.GetNextEntry((void**)_inode); if (status == B_OK) { if (Acquire(*_inode) == NULL) return B_NO_MEMORY; } return status; } void Rewind() { fHashtable.Rewind(); } bool IsEmpty() const { return fHashtable.IsEmpty(); } void MakeEmpty() { fHashtable.MakeEmpty(HASH_EMPTY_NONE, HASH_EMPTY_DELETE); } static uint32 BlockRunHash(const block_run *run) { return (run->allocation_group << 16) | run->start; } static bool BlockRunCompare(const block_run *runA, const block_run *runB) { return *runA == *runB; } private: Hashtable fHashtable; bigtime_t fLastChecked; uint32 fPercentUsed; }; class InodeGetter { public: InodeGetter(Disk& disk, block_run run) { fInode = Inode::Factory(&disk, run); if (fInode != NULL) fInode->AcquireBuffer(); } ~InodeGetter() { if (fInode != NULL) fInode->ReleaseBuffer(); } Inode* Node() const { return fInode; } void Detach() { fInode = NULL; } private: Inode* fInode; }; RunSet gMainInodes; // contains all inodes found on disk in the general data area InodeHashtable gLogged(50); // contains all inodes found in the log area InodeHashtable gMissing(50); InodeHashtable gMissingEmpty(25); class HashtableInodeSource : public Inode::Source { public: HashtableInodeSource(Disk& disk) : fDisk(disk) { } virtual Inode *InodeAt(block_run run) { Inode *inode; if ((inode = gLogged.Get(run)) != NULL) return Inode::Factory(&fDisk, inode, false); if ((inode = gMissing.Get(run)) != NULL) return Inode::Factory(&fDisk, inode, false); if (gMainInodes.find(run) == gMainInodes.end()) return NULL; return Inode::Factory(&fDisk, run); } private: Disk& fDisk; }; bool operator<(const block_run& a, const block_run& b) { return a.allocation_group < b.allocation_group || (a.allocation_group == b.allocation_group && a.start < b.start); } void collectInodes(Disk& disk, RunSet* set, InodeHashtable* hashTable, off_t start, off_t end) { char buffer[8192]; Inode inode(&disk, (bfs_inode *)buffer, false); off_t directories = 0LL; off_t directorySize = 0LL; off_t files = 0LL; off_t fileSize = 0LL; off_t symlinks = 0LL; off_t count = 0LL; off_t position = start; bigtime_t lastUpdate = system_time(); for (off_t offset = start; offset < end; offset += sizeof(buffer)) { if (disk.ReadAt(offset, buffer, sizeof(buffer)) < B_OK) { fprintf(stderr, "could not read from device!\n"); break; } //if ((offset % (disk.BlockSize() << disk.SuperBlock()->ag_shift)) == 0) // printf("reading block %lld, allocation group %lld, %lld inodes...\33[1A\n", offset / disk.BlockSize(),offset / (disk.BlockSize() << disk.SuperBlock()->ag_shift), count); for (uint32 i = 0; i < sizeof(buffer); i += disk.BlockSize()) { inode.SetTo((bfs_inode *)(buffer + i)); if (inode.InitCheck() == B_OK) { if (inode.Flags() & INODE_DELETED) continue; Inode *node = Inode::Factory(&disk, &inode); if (node != NULL) { if (gVerbose) printf(" node: %" B_PRIdOFF " \"%s\"\n", position, node->Name()); if (set != NULL) set->insert(node->BlockRun()); else hashTable->Insert(node); if (node->IsDirectory()) { directories++; directorySize += node->Size(); } else if (node->IsFile()) { files++; fileSize += node->Size(); } else if (node->IsSymlink()) { symlinks++; } count++; } else if (gVerbose) { printf("\nunrecognized inode:"); dump_inode(&inode, inode.InodeBuffer()); } } position += disk.BlockSize(); } if (system_time() - lastUpdate > 500000) { printf(" block %" B_PRIdOFF " (%" B_PRIdOFF "%%), %" B_PRIdOFF " inodes\33[1A\n", offset, 100 * (offset - start) / (end - start), count); lastUpdate = system_time(); } } printf("\n%" B_PRIdOFF " inodes found.\n", count); printf("\n%20" B_PRIdOFF " directories found (total of %" B_PRIdOFF " bytes)\n%20" B_PRIdOFF " files found (total of %" B_PRIdOFF " bytes)\n%20" B_PRIdOFF " symlinks found\n" "--------------------\n" "%20" B_PRIdOFF " inodes total found.\n", directories, directorySize, files, fileSize, symlinks, count); } void collectLogInodes(Disk &disk) { // scan log area off_t offset = disk.ToOffset(disk.Log()); off_t end = offset + (disk.Log().length << disk.BlockShift()); printf("\nsearching from %" B_PRIdOFF " to %" B_PRIdOFF " (log area)\n", offset, end); collectInodes(disk, NULL, &gLogged, offset, end); } void collectRealInodes(Disk &disk) { // first block after bootblock, bitmap, and log off_t offset = disk.ToOffset(disk.Log()) + (disk.Log().length << disk.BlockShift()); off_t end = (off_t)disk.NumBlocks() << disk.BlockShift(); printf("\nsearching from %" B_PRIdOFF " to %" B_PRIdOFF " (main area)\n", offset, end); collectInodes(disk, &gMainInodes, NULL, offset, end); } Directory * getNameIndex(Disk &disk) { InodeGetter getter(disk, disk.Indices()); Directory *indices = dynamic_cast(getter.Node()); block_run run; if (indices != NULL && indices->FindEntry("name", &run) == B_OK) { InodeGetter getter(disk, run); Inode* node = getter.Node(); getter.Detach(); return dynamic_cast(node); } // search name index RunSet::iterator iterator = gMainInodes.begin(); for (; iterator != gMainInodes.end(); iterator++) { InodeGetter getter(disk, *iterator); Inode* node = getter.Node(); if (!node || !node->IsIndex() || node->Name() == NULL) continue; if (!strcmp(node->Name(), "name") && node->Mode() & S_STR_INDEX) return dynamic_cast(node); } return NULL; } void checkDirectoryContents(Disk& disk, Directory *dir) { dir->Rewind(); char name[B_FILE_NAME_LENGTH]; block_run run; while (dir->GetNextEntry(name, &run) == B_OK) { if (run == dir->BlockRun() || run == dir->Parent() || gMainInodes.find(run) != gMainInodes.end()) continue; Inode *missing = gMissing.Get(run); if (missing != NULL) { if (missing->SetName(name) < B_OK) { fprintf(stderr, "setting name of missing node to " "\"%s\" failed!", name); } if (gVerbose) { printf("Set name of missing node (%" B_PRId32 ", %d) to \"%s\" (%s)\n", run.allocation_group, run.start, missing->Name(), name); } missing->SetParent(dir->BlockRun()); } // if (node->Mode() & S_INDEX_DIR) // { // if (node->Mode() & S_STR_INDEX) // 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); // else // 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); // } else { // missing inode has not been found if (gVerbose) { printf("directory \"%s\" (%" B_PRId32 ", %d): node \"%s\" is " "missing (%" B_PRId32 ", %d, %d)\n", dir->Name(), dir->BlockRun().allocation_group, dir->BlockRun().start, name, run.allocation_group, run.start, run.length); } if ((missing = (Inode *)gLogged.Remove(&run)) != NULL) { // missing inode is in the log if (gVerbose) printf("found missing entry in log!\n"); if (missing->InodeBuffer()->parent != dir->BlockRun()) { if (gVerbose) puts("\tparent directories differ (may be an old " "version of it), reseting parent."); missing->SetParent(dir->BlockRun()); } if (!gMissing.Insert(missing)) delete missing; } else if (!gMissingEmpty.Contains(&run)) { // not known at all yet Inode *empty = Inode::EmptyInode(&disk, name, 0); if (empty) { empty->SetBlockRun(run); empty->SetParent(dir->BlockRun()); if (gVerbose) printf("\tname = %s\n", empty->Name()); if (!gMissingEmpty.Insert(empty)) delete empty; } } } } } void checkStructure(Disk &disk) { off_t count = 0; Inode* node; RunSet::iterator iterator = gMainInodes.begin(); for (; iterator != gMainInodes.end(); iterator++) { InodeGetter getter(disk, *iterator); node = getter.Node(); count++; if ((count % 50) == 0) fprintf(stderr, "%" B_PRIdOFF " inodes processed...\33[1A\n", count); if (node == NULL) continue; if (node->IsDirectory() && !node->IsIndex()) { // check if all entries are in the hashtable Directory* directory = dynamic_cast(node); if (directory != NULL) checkDirectoryContents(disk, directory); else { printf("Node \"%s\" at %" B_PRId32 ",%d looks like a directory, but isn't.\n", node->Name(), node->BlockRun().allocation_group, node->BlockRun().start); } } // check for the parent directory block_run run = node->Parent(); InodeGetter parentGetter(disk, run); Inode *parentNode = parentGetter.Node(); Directory *dir = dynamic_cast(parentNode); if (dir || (parentNode && (node->Mode() & S_ATTR_DIR))) { // entry has a valid parent directory, so it's assumed to be a valid entry disk.BlockBitmap()->BackupSet(node, true); } else if (node->Mode() & S_ATTR) { if (gVerbose) { printf("attribute \"%s\" at %" B_PRId32 ",%d misses its parent.\n", node->Name(), node->BlockRun().allocation_group, node->BlockRun().start); puts("\thandling not yet implemented..."); } } else /*if ((node->Flags() & INODE_DELETED) == 0)*/ { Inode* missing = gMissing.Get(run); dir = dynamic_cast(missing); if (missing == NULL) { if (gVerbose) { printf("%s \"%s\" (%" B_PRId32 ", %d, mode = %10" B_PRIo32 "): parent directory is missing (%" B_PRId32 ", %d, %d), may be deleted!\n", node->IsFile() ? "file" : "node", node->Name(), node->BlockRun().allocation_group, node->BlockRun().start, node->Mode(), run.allocation_group, run.start, run.length); } if ((dir = dynamic_cast((Inode *)gLogged.Remove( &run))) != NULL) { if (gVerbose) { printf("found directory \"%s\" in log:\n", dir->Name()); if (dir->Size() > 0) dump_inode(dir, dir->InodeBuffer()); else puts("\tempty inode."); } } else { if (gVerbose) puts("\tcreate parent missing entry"); Inode *nameNode = (Inode *)gMissingEmpty.Remove(&run); if (nameNode != NULL) { nameNode->SetMode(S_IFDIR); if (gVerbose) printf("found missing name!\n"); } else { BString parentName; parentName << "__directory " << run.allocation_group << ":" << (int32)run.start; nameNode = Inode::EmptyInode(&disk, parentName.String(), S_IFDIR); if (nameNode) { nameNode->SetBlockRun(run); nameNode->SetParent(disk.Root()); } } if (nameNode) { dir = new Directory(*nameNode); if (dir->CopyBuffer() < B_OK) puts("could not copy buffer!"); else delete nameNode; } } if (dir) { dir->AcquireBuffer(); if (!gMissing.Insert(dir)) { printf("could not add dir!!\n"); delete dir; dir = NULL; } } } else if (missing != NULL && dir == NULL && gVerbose) { printf("%s \"%s\" (%" B_PRId32 ", %d, mode = %10" B_PRIo32 "): parent directory found in missing list (%" B_PRId32 ", %d, %d), but it's not a dir!\n", node->IsFile() ? "file" : "node", node->Name(), node->BlockRun().allocation_group, node->BlockRun().start, node->Mode(), run.allocation_group, run.start, run.length); } else if (gVerbose) { printf("%s \"%s\" (%" B_PRId32 ", %d, mode = %10" B_PRIo32 "): parent directory found in missing list (%" B_PRId32 ", %d, %d)!\n", node->IsFile() ? "file" : "node", node->Name(), node->BlockRun().allocation_group, node->BlockRun().start, node->Mode(), run.allocation_group, run.start, run.length); } if (dir) { dir->AddEntry(node); dir->ReleaseBuffer(); } } // else // { // printf("node %s\n", node->Name()); // status_t status = dir->Contains(node); // if (status == B_ENTRY_NOT_FOUND) // printf("node \"%s\": parent directory \"%s\" contains no link to this node!\n",node->Name(),dir->Name()); // else if (status != B_OK) // printf("node \"%s\": parent directory \"%s\" error: %s\n",node->Name(),dir->Name(),strerror(status)); // } // check for attributes run = node->Attributes(); if (!run.IsZero()) { //printf("node \"%s\" (%ld, %d, mode = %010lo): has attribute dir!\n",node->Name(),node->BlockRun().allocation_group,node->BlockRun().start,node->Mode()); if (gMainInodes.find(run) == gMainInodes.end()) { if (gVerbose) { printf("node \"%s\": attributes are missing (%" B_PRId32 ", %d, %d)\n", node->Name(), run.allocation_group, run.start, run.length); } if ((dir = (Directory *)gMissing.Get(run)) != NULL) { if (gVerbose) puts("\tfound in missing"); dir->SetMode(dir->Mode() | S_ATTR_DIR); dir->SetParent(node->BlockRun()); } else { if (gVerbose) puts("\tcreate new!"); Inode *empty = Inode::EmptyInode(&disk, NULL, S_IFDIR | S_ATTR_DIR); if (empty) { empty->SetBlockRun(run); empty->SetParent(node->BlockRun()); dir = new Directory(*empty); if (dir->CopyBuffer() < B_OK) puts("could not copy buffer!"); else delete empty; if (!gMissing.Insert(dir)) { puts("\tcould not add attribute dir"); delete dir; } } } } } } printf("%" B_PRIdOFF " inodes processed.\n", count); Directory *directory = getNameIndex(disk); if (directory != NULL) { puts("\n*** Search names of missing inodes in the name index"); BPlusTree *tree; if (directory->GetTree(&tree) == B_OK && tree->Validate(gVerbose) == B_OK) { char name[B_FILE_NAME_LENGTH]; block_run run; directory->Rewind(); while (directory->GetNextEntry(name, &run) >= B_OK) { if ((node = gMissing.Get(run)) == NULL) continue; if (gVerbose) { printf(" Node found: %" B_PRId32 ":%d\n", run.allocation_group, run.start); } if (node->Name() == NULL || strcmp(node->Name(), name)) { if (gVerbose) { printf("\tnames differ: %s -> %s (indices)\n", node->Name(), name); } node->SetName(name); } } } else printf("\tname index is corrupt!\n"); directory->ReleaseBuffer(); } else printf("*** Name index corrupt or not existent!\n"); if (!gVerbose) return; if (!gMissing.IsEmpty()) puts("\n*** Missing inodes:"); gMissing.Rewind(); while (gMissing.GetNextEntry(&node) == B_OK) { if (gDumpMissingInodes) dump_inode(node, node->InodeBuffer()); Directory *dir = dynamic_cast(node); if (dir) { printf("\ndirectory \"%s\" (%" B_PRId32 ", %d) contents:\n", node->Name(), node->BlockRun().allocation_group, node->BlockRun().start); dir->Rewind(); char name[B_FILE_NAME_LENGTH]; block_run run; while (dir->GetNextEntry(name, &run) == B_OK) { printf("\t\"%s\" (%" B_PRId32 ", %d, %d)\n", name, run.allocation_group, run.start, run.length); } BPlusTree *tree; if (dir->GetTree(&tree) < B_OK) continue; uint16 length; off_t offset; while (tree->GetNextEntry(name, &length, B_FILE_NAME_LENGTH, &offset) == B_OK) { name[length] = 0; run = disk.ToBlockRun(offset); printf("%s: block_run == (%5" B_PRId32 ",%5d,%5d), \"%s\"\n", dir->Name(), run.allocation_group, run.start, run.length, name); } //tree->WriteTo(dir); //disk.WriteAt(dir->Offset(),dir->InodeBuffer(),disk.BlockSize()); } gMissing.Release(node); } } void copyInodes(Disk& disk, const char* copyTo) { if (copyTo == NULL) return; HashtableInodeSource source(disk); Inode *node; int32 count = 0; RunSet::iterator iterator = gMainInodes.begin(); for (; iterator != gMainInodes.end(); iterator++) { InodeGetter getter(disk, *iterator); Inode* node = getter.Node(); if (node && !node->IsIndex() && !node->IsAttributeDirectory()) node->CopyTo(copyTo, true, &source); if ((++count % 500) == 0) fprintf(stderr, "copied %" B_PRId32 " files...\n", count); } gMissing.Rewind(); while (gMissing.GetNextEntry(&node) == B_OK) { if (!node->IsIndex() && !node->IsAttributeDirectory()) node->CopyTo(copyTo, true, &source); gMissing.Release(node); } } void usage(char *name) { fprintf(stderr,"usage: %s [-idv] [-r [start-offset] [end-offset]] [recover-to-path]\n" "\t-i\trecreate indices on target disk\n" "\t-d\tdump missing and recreated i-nodes\n" "\t-r\tdisk access in raw mode (use only if the partition table is invalid)\n" "\t-s\trecreate superblock and exit (for experts only, don't use this\n" "\t\tif you don't know what you're doing)\n" "\t-v\tverbose output\n", name); exit(-1); } int main(int argc, char **argv) { char *fileName = strrchr(argv[0], '/'); fileName = fileName ? fileName + 1 : argv[0]; bool recreateSuperBlockOnly = false; off_t startOffset = 0, endOffset = -1; puts("Copyright (c) 2001-2008 pinc Software."); if (argc < 2 || !strcmp(argv[1], "--help")) usage(fileName); while (*++argv) { char *arg = *argv; if (*arg == '-') { while (*++arg && isalpha(*arg)) { switch (arg[0]) { case 'r': { gRawMode = true; if (argv[1] && isdigit((argv[1])[0])) { argv++; arg = *argv; startOffset = atoll(arg); } if (argv[1] && isdigit((argv[1])[0])) { argv++; arg = *argv; endOffset = atoll(arg); } if (endOffset != -1 && endOffset < startOffset) usage(fileName); break; } case 'v': gVerbose = true; break; case 'i': gCreateIndices = true; break; case 'd': gDumpMissingInodes = true; break; case 's': recreateSuperBlockOnly = true; break; } } } else break; } Disk disk(argv[0], gRawMode, startOffset, endOffset); if (disk.InitCheck() < B_OK) { fprintf(stderr,"Could not open device or file: %s\n", strerror(disk.InitCheck())); return -1; } if (argv[1] != NULL) { dev_t device = dev_for_path(argv[1]); fs_info info; if (fs_stat_dev(device, &info) == B_OK) { if (!strcmp(info.device_name, disk.Path().Path())) { fprintf(stderr,"The source and target device are identical, " "you currently need\n" "to have another disk to recover to, sorry!\n"); return -1; } if ((info.flags & (B_FS_IS_PERSISTENT | B_FS_HAS_ATTR)) != (B_FS_IS_PERSISTENT | B_FS_HAS_ATTR)) { fprintf(stderr, "%s: The target file system (%s) does not have " "the required\n" "\tfunctionality in order to restore all information.\n", kProgramName, info.fsh_name); return -1; } } } bool recreatedSuperBlock = false; if (disk.ValidateSuperBlock() < B_OK) { fprintf(stderr, "The disk's superblock is corrupt!\n"); if (disk.RecreateSuperBlock() < B_OK) { fprintf(stderr, "Can't recreate the disk's superblock, sorry!\n"); return -1; } recreatedSuperBlock = true; } if (gVerbose) { puts("\n*** The superblock:\n"); dump_super_block(disk.SuperBlock()); } if (recreateSuperBlockOnly) { if (!recreatedSuperBlock) { printf("Superblock was valid, no changes made.\n"); return 0; } status_t status = disk.WriteAt(512, disk.SuperBlock(), sizeof(disk_super_block)); if (status < B_OK) { fprintf(stderr, "Could not write superblock: %s!\n", strerror(status)); return 1; } return 0; } puts("\n*** Collecting inodes..."); collectLogInodes(disk); collectRealInodes(disk); puts("\n*** Checking Disk Structure Integrity..."); checkStructure(disk); if (argv[1]) copyInodes(disk, argv[1]); //disk.WriteBootBlock(); //disk.BlockBitmap()->CompareWithBackup(); gMissing.MakeEmpty(); gLogged.MakeEmpty(); return 0; }