/* * Check the directory entry in SFD. INDEX is its offset, and PATH is * its name; these are used for printing messages. */ static int pass1_direntry(const char *path, uint32_t index, struct sfs_dir *sfd) { int dchanged = 0; uint32_t nblocks; nblocks = sb_totalblocks(); if (sfd->sfd_ino == SFS_NOINO) { if (sfd->sfd_name[0] != 0) { setbadness(EXIT_RECOV); warnx("Directory %s entry %lu has name but no file", path, (unsigned long) index); sfd->sfd_name[0] = 0; dchanged = 1; } } else if (sfd->sfd_ino >= nblocks) { setbadness(EXIT_RECOV); warnx("Directory %s entry %lu has out of range " "inode (cleared)", path, (unsigned long) index); sfd->sfd_ino = SFS_NOINO; sfd->sfd_name[0] = 0; dchanged = 1; } else { if (sfd->sfd_name[0] == 0) { /* XXX: what happens if FSCK.n.m already exists? */ snprintf(sfd->sfd_name, sizeof(sfd->sfd_name), "FSCK.%lu.%lu", (unsigned long) sfd->sfd_ino, (unsigned long) uniqueid()); setbadness(EXIT_RECOV); warnx("Directory %s entry %lu has file but " "no name (fixed: %s)", path, (unsigned long) index, sfd->sfd_name); dchanged = 1; } if (checknullstring(sfd->sfd_name, sizeof(sfd->sfd_name))) { setbadness(EXIT_RECOV); warnx("Directory %s entry %lu not " "null-terminated (fixed)", path, (unsigned long) index); dchanged = 1; } if (checkbadstring(sfd->sfd_name)) { setbadness(EXIT_RECOV); warnx("Directory %s entry %lu contains invalid " "characters (fixed)", path, (unsigned long) index); dchanged = 1; } } return dchanged; }
static void dirwrite(const struct sfs_inode *sfi, struct sfs_dir *d, int nd) { const unsigned atonce = SFS_BLOCKSIZE/sizeof(struct sfs_dir); unsigned nblocks = SFS_ROUNDUP(nd, atonce) / atonce; unsigned i, j, bad; for (i=0; i<nblocks; i++) { uint32_t block = dobmap(sfi, i); if (block!=0) { for (j=0; j<atonce; j++) { swapdir(&d[i*atonce+j]); } diskwrite(d + i*atonce, block); } else { for (j=bad=0; j<atonce; j++) { if (d[i*atonce+j].sfd_ino != SFS_NOINO || d[i*atonce+j].sfd_name[0] != 0) { bad = 1; } } if (bad) { warnx("Cannot write to missing block in " "sparse directory (ERROR)"); setbadness(EXIT_UNRECOV); } } } }
static void adjust_filelinks(void) { struct sfs_inode sfi; int i; for (i=0; i<ninodes; i++) { if (inodes[i].linkcount==0) { /* directory */ continue; } diskread(&sfi, inodes[i].ino); swapinode(&sfi); assert(sfi.sfi_type == SFS_TYPE_FILE); if (sfi.sfi_linkcount != inodes[i].linkcount) { warnx("File %lu link count %lu should be %lu (fixed)", (unsigned long) inodes[i].ino, (unsigned long) sfi.sfi_linkcount, (unsigned long) inodes[i].linkcount); sfi.sfi_linkcount = inodes[i].linkcount; setbadness(EXIT_RECOV); swapinode(&sfi); diskwrite(&sfi, inodes[i].ino); } count_files++; } }
static void check_root_dir(void) { struct sfs_inode sfi; diskread(&sfi, SFS_ROOT_LOCATION); swapinode(&sfi); switch (sfi.sfi_type) { case SFS_TYPE_DIR: break; case SFS_TYPE_FILE: warnx("Root directory inode is a regular file (fixed)"); goto fix; default: warnx("Root directory inode has invalid type %lu (fixed)", (unsigned long) sfi.sfi_type); fix: setbadness(EXIT_RECOV); sfi.sfi_type = SFS_TYPE_DIR; swapinode(&sfi); diskwrite(&sfi, SFS_ROOT_LOCATION); break; } check_dir(SFS_ROOT_LOCATION, SFS_ROOT_LOCATION, ""); }
/* * Correct link counts. This is effectively pass3. (FUTURE: change the * name accordingly.) */ void inode_adjust_filelinks(void) { struct sfs_dinode sfi; unsigned i; for (i=0; i<ninodes; i++) { if (inodes[i].type == SFS_TYPE_DIR) { /* directory */ continue; } assert(inodes[i].type == SFS_TYPE_FILE); /* because we've seen it, there must be at least one link */ assert(inodes[i].linkcount > 0); sfs_readinode(inodes[i].ino, &sfi); assert(sfi.sfi_type == SFS_TYPE_FILE); if (sfi.sfi_linkcount != inodes[i].linkcount) { warnx("File %lu link count %lu should be %lu (fixed)", (unsigned long) inodes[i].ino, (unsigned long) sfi.sfi_linkcount, (unsigned long) inodes[i].linkcount); sfi.sfi_linkcount = inodes[i].linkcount; setbadness(EXIT_RECOV); sfs_writeinode(inodes[i].ino, &sfi); } } }
/* * Check the root directory, and implicitly everything under it. */ static void pass1_rootdir(void) { struct sfs_dinode sfi; char path[SFS_VOLNAME_SIZE + 2]; sfs_readinode(SFS_ROOT_LOCATION, &sfi); switch (sfi.sfi_type) { case SFS_TYPE_DIR: break; case SFS_TYPE_FILE: warnx("Root directory inode is a regular file (fixed)"); goto fix; default: warnx("Root directory inode has invalid type %lu (fixed)", (unsigned long) sfi.sfi_type); fix: setbadness(EXIT_RECOV); sfi.sfi_type = SFS_TYPE_DIR; sfs_writeinode(SFS_ROOT_LOCATION, &sfi); break; } snprintf(path, sizeof(path), "%s:", sb_volname()); pass1_dir(SFS_ROOT_LOCATION, path); }
/* * Do the pass1 inode-level checks on inode INO, which has already * been loaded into SFI. Note that sfi_type has already been * validated. * * Returns nonzero if SFI has been modified and needs to be written * back. */ static int pass1_inode(uint32_t ino, struct sfs_dinode *sfi, int alreadychanged) { int changed = alreadychanged; int isdir = sfi->sfi_type == SFS_TYPE_DIR; if (inode_add(ino, sfi->sfi_type)) { /* Already been here. */ assert(changed == 0); return 1; } bitmap_blockinuse(ino, B_INODE, ino); if (checkzeroed(sfi->sfi_waste, sizeof(sfi->sfi_waste))) { warnx("Inode %lu: sfi_waste section not zeroed (fixed)", (unsigned long) ino); setbadness(EXIT_RECOV); changed = 1; } if (check_inode_blocks(ino, sfi, isdir)) { changed = 1; } if (changed) { sfs_writeinode(ino, sfi); } return 0; }
static void check_sb(void) { struct sfs_super sp; uint32_t i; int schanged=0; diskread(&sp, SFS_SB_LOCATION); swapsb(&sp); if (sp.sp_magic != SFS_MAGIC) { errx(EXIT_UNRECOV, "Not an sfs filesystem"); } assert(nblocks==0); assert(bitblocks==0); nblocks = sp.sp_nblocks; bitblocks = SFS_BITBLOCKS(nblocks); assert(nblocks>0); assert(bitblocks>0); bitmap_init(bitblocks); for (i=nblocks; i<bitblocks*SFS_BLOCKBITS; i++) { bitmap_mark(i, B_PASTEND, 0); } if (checknullstring(sp.sp_volname, sizeof(sp.sp_volname))) { warnx("Volume name not null-terminated (fixed)"); setbadness(EXIT_RECOV); schanged = 1; } if (checkbadstring(sp.sp_volname)) { warnx("Volume name contains illegal characters (fixed)"); setbadness(EXIT_RECOV); schanged = 1; } if (schanged) { swapsb(&sp); diskwrite(&sp, SFS_SB_LOCATION); } bitmap_mark(SFS_SB_LOCATION, B_SUPERBLOCK, 0); for (i=0; i<bitblocks; i++) { bitmap_mark(SFS_MAP_LOCATION+i, B_BITBLOCK, i); } }
static int check_dir_entry(const char *pathsofar, uint32_t index, struct sfs_dir *sfd) { int dchanged = 0; if (sfd->sfd_ino == SFS_NOINO) { if (sfd->sfd_name[0] != 0) { setbadness(EXIT_RECOV); warnx("Directory /%s entry %lu has name but no file", pathsofar, (unsigned long) index); sfd->sfd_name[0] = 0; dchanged = 1; } } else { if (sfd->sfd_name[0] == 0) { snprintf(sfd->sfd_name, sizeof(sfd->sfd_name), "FSCK.%lu.%lu", (unsigned long) sfd->sfd_ino, (unsigned long) uniquecounter++); setbadness(EXIT_RECOV); warnx("Directory /%s entry %lu has file but " "no name (fixed: %s)", pathsofar, (unsigned long) index, sfd->sfd_name); dchanged = 1; } if (checknullstring(sfd->sfd_name, sizeof(sfd->sfd_name))) { setbadness(EXIT_RECOV); warnx("Directory /%s entry %lu not " "null-terminated (fixed)", pathsofar, (unsigned long) index); dchanged = 1; } if (checkbadstring(sfd->sfd_name)) { setbadness(EXIT_RECOV); warnx("Directory /%s entry %lu contains invalid " "characters (fixed)", pathsofar, (unsigned long) index); dchanged = 1; } } return dchanged; }
static void bitmap_mark(uint32_t block, blockusage_t how, uint32_t howdesc) { unsigned index = block/8; uint8_t mask = ((uint8_t)1)<<(block%8); if (how == B_TOFREE) { if (tofreedata[index] & mask) { /* already marked to free once, ignore */ return; } if (bitmapdata[index] & mask) { /* block is used elsewhere, ignore */ return; } tofreedata[index] |= mask; return; } if (tofreedata[index] & mask) { /* really using the block, don't free it */ tofreedata[index] &= ~mask; } if (bitmapdata[index] & mask) { warnx("Block %lu (used as %s) already in use! (NOT FIXED)", (unsigned long) block, blockusagestr(how, howdesc)); setbadness(EXIT_UNRECOV); } bitmapdata[index] |= mask; if (how != B_PASTEND) { count_blocks++; } }
/* returns nonzero if inode modified */ static int check_inode_blocks(uint32_t ino, struct sfs_inode *sfi, int isdir) { uint32_t size, block, nblocks, badcount; badcount = 0; size = SFS_ROUNDUP(sfi->sfi_size, SFS_BLOCKSIZE); nblocks = size/SFS_BLOCKSIZE; for (block=0; block<SFS_NDIRECT; block++) { if (block < nblocks) { if (sfi->sfi_direct[block] != 0) { bitmap_mark(sfi->sfi_direct[block], isdir ? B_DIRDATA : B_DATA, ino); } } else { if (sfi->sfi_direct[block] != 0) { badcount++; bitmap_mark(sfi->sfi_direct[block], B_TOFREE, 0); } } } #ifdef SFS_NIDIRECT for (i=0; i<SFS_NIDIRECT; i++) { check_indirect_block(ino, &sfi->sfi_indirect[i], &block, nblocks, &badcount, isdir, 1); } #else check_indirect_block(ino, &sfi->sfi_indirect, &block, nblocks, &badcount, isdir, 1); #endif #ifdef SFS_NDIDIRECT for (i=0; i<SFS_NDIDIRECT; i++) { check_indirect_block(ino, &sfi->sfi_dindirect[i], &block, nblocks, &badcount, isdir, 2); } #else #ifdef HAS_DIDIRECT check_indirect_block(ino, &sfi->sfi_dindirect, &block, nblocks, &badcount, isdir, 2); #endif #endif #ifdef SFS_NTIDIRECT for (i=0; i<SFS_NTIDIRECT; i++) { check_indirect_block(ino, &sfi->sfi_tindirect[i], &block, nblocks, &badcount, isdir, 3); } #else #ifdef HAS_TIDIRECT check_indirect_block(ino, &sfi->sfi_tindirect, &block, nblocks, &badcount, isdir, 3); #endif #endif if (badcount > 0) { warnx("Inode %lu: %lu blocks after EOF (freed)", (unsigned long) ino, (unsigned long) badcount); setbadness(EXIT_RECOV); return 1; } return 0; }
static void check_bitmap(void) { uint8_t bits[SFS_BLOCKSIZE], *found, *tofree, tmp; uint32_t alloccount=0, freecount=0, i, j; int bchanged; for (i=0; i<bitblocks; i++) { diskread(bits, SFS_MAP_LOCATION+i); swapbits(bits); found = bitmapdata + i*SFS_BLOCKSIZE; tofree = tofreedata + i*SFS_BLOCKSIZE; bchanged = 0; for (j=0; j<SFS_BLOCKSIZE; j++) { /* we shouldn't have blocks marked both ways */ assert((found[j] & tofree[j])==0); if (bits[j]==found[j]) { continue; } if (bits[j]==(found[j] | tofree[j])) { bits[j] = found[j]; bchanged = 1; continue; } /* free the ones we're freeing */ bits[j] &= ~tofree[j]; /* are we short any? */ if ((bits[j] & found[j]) != found[j]) { tmp = found[j] & ~bits[j]; alloccount += countbits(tmp); if (tmp != 0) { reportbits(i, j, tmp, "free"); } } /* do we have any extra? */ if ((bits[j] & found[j]) != bits[j]) { tmp = bits[j] & ~found[j]; freecount += countbits(tmp); if (tmp != 0) { reportbits(i, j, tmp, "allocated"); } } bits[j] = found[j]; bchanged = 1; } if (bchanged) { swapbits(bits); diskwrite(bits, SFS_MAP_LOCATION+i); } } if (alloccount > 0) { warnx("%lu blocks erroneously shown free in bitmap (fixed)", (unsigned long) alloccount); setbadness(EXIT_RECOV); } if (freecount > 0) { warnx("%lu blocks erroneously shown used in bitmap (fixed)", (unsigned long) freecount); setbadness(EXIT_RECOV); } }
/* * Check the blocks belonging to inode INO, whose inode has already * been loaded into SFI. ISDIR is a shortcut telling us if the inode * is a directory. * * Returns nonzero if SFI has been modified and needs to be written * back. */ static int check_inode_blocks(uint32_t ino, struct sfs_dinode *sfi, int isdir) { struct ibstate ibs; uint32_t size, datablock; int changed; int i; size = SFS_ROUNDUP(sfi->sfi_size, SFS_BLOCKSIZE); ibs.ino = ino; /*ibs.curfileblock = 0;*/ ibs.fileblocks = size/SFS_BLOCKSIZE; ibs.volblocks = sb_totalblocks(); ibs.pasteofcount = 0; ibs.usagetype = isdir ? B_DIRDATA : B_DATA; changed = 0; for (ibs.curfileblock=0; ibs.curfileblock<NUM_D; ibs.curfileblock++) { datablock = GET_D(sfi, ibs.curfileblock); if (datablock >= ibs.volblocks) { warnx("Inode %lu: direct block pointer for " "block %lu outside of volume " "(cleared)\n", (unsigned long)ibs.ino, (unsigned long)ibs.curfileblock); SET_D(sfi, ibs.curfileblock) = 0; changed = 1; } else if (datablock > 0) { if (ibs.curfileblock < ibs.fileblocks) { bitmap_blockinuse(datablock, ibs.usagetype, ibs.ino); } else { ibs.pasteofcount++; changed = 1; bitmap_blockfree(datablock); SET_D(sfi, ibs.curfileblock) = 0; } } } for (i=0; i<NUM_I; i++) { check_indirect_block(&ibs, &SET_I(sfi, i), &changed, 1); } for (i=0; i<NUM_II; i++) { check_indirect_block(&ibs, &SET_II(sfi, i), &changed, 2); } for (i=0; i<NUM_III; i++) { check_indirect_block(&ibs, &SET_III(sfi, i), &changed, 3); } if (ibs.pasteofcount > 0) { warnx("Inode %lu: %u blocks after EOF (freed)", (unsigned long) ibs.ino, ibs.pasteofcount); setbadness(EXIT_RECOV); } return changed; }
/* * Check a directory. INO is the inode number; PATHSOFAR is the path * to this directory. This traverses the volume directory tree * recursively. */ static void pass1_dir(uint32_t ino, const char *pathsofar) { struct sfs_dinode sfi; struct sfs_dir *direntries; uint32_t ndirentries, i; int ichanged=0, dchanged=0; sfs_readinode(ino, &sfi); if (sfi.sfi_size % sizeof(struct sfs_dir) != 0) { setbadness(EXIT_RECOV); warnx("Directory %s has illegal size %lu (fixed)", pathsofar, (unsigned long) sfi.sfi_size); sfi.sfi_size = SFS_ROUNDUP(sfi.sfi_size, sizeof(struct sfs_dir)); ichanged = 1; } count_dirs++; if (pass1_inode(ino, &sfi, ichanged)) { /* been here before; crosslinked dir, sort it out in pass 2 */ return; } ndirentries = sfi.sfi_size/sizeof(struct sfs_dir); direntries = domalloc(sfi.sfi_size); sfs_readdir(&sfi, direntries, ndirentries); for (i=0; i<ndirentries; i++) { if (pass1_direntry(pathsofar, i, &direntries[i])) { dchanged = 1; } } for (i=0; i<ndirentries; i++) { if (direntries[i].sfd_ino == SFS_NOINO) { /* nothing */ } else if (!strcmp(direntries[i].sfd_name, ".")) { /* nothing */ } else if (!strcmp(direntries[i].sfd_name, "..")) { /* nothing */ } else { char path[strlen(pathsofar)+SFS_NAMELEN+1]; struct sfs_dinode subsfi; uint32_t subino; subino = direntries[i].sfd_ino; sfs_readinode(subino, &subsfi); snprintf(path, sizeof(path), "%s/%s", pathsofar, direntries[i].sfd_name); switch (subsfi.sfi_type) { case SFS_TYPE_FILE: if (pass1_inode(subino, &subsfi, 0)) { /* been here before */ break; } count_files++; break; case SFS_TYPE_DIR: pass1_dir(subino, path); break; default: setbadness(EXIT_RECOV); warnx("Object %s: Invalid inode type " "(removed)", path); direntries[i].sfd_ino = SFS_NOINO; direntries[i].sfd_name[0] = 0; dchanged = 1; break; } } } if (dchanged) { sfs_writedir(&sfi, direntries, ndirentries); } free(direntries); }
static int check_dir(uint32_t ino, uint32_t parentino, const char *pathsofar) { struct sfs_inode sfi; struct sfs_dir *direntries; int *sortvector; uint32_t dirsize, ndirentries, maxdirentries, subdircount, i; int ichanged=0, dchanged=0, dotseen=0, dotdotseen=0; diskread(&sfi, ino); swapinode(&sfi); if (remember_dir(ino, pathsofar)) { /* crosslinked dir */ return 1; } bitmap_mark(ino, B_INODE, ino); count_dirs++; if (sfi.sfi_size % sizeof(struct sfs_dir) != 0) { setbadness(EXIT_RECOV); warnx("Directory /%s has illegal size %lu (fixed)", pathsofar, (unsigned long) sfi.sfi_size); sfi.sfi_size = SFS_ROUNDUP(sfi.sfi_size, sizeof(struct sfs_dir)); ichanged = 1; } if (check_inode_blocks(ino, &sfi, 1)) { ichanged = 1; } ndirentries = sfi.sfi_size/sizeof(struct sfs_dir); maxdirentries = SFS_ROUNDUP(ndirentries, SFS_BLOCKSIZE/sizeof(struct sfs_dir)); dirsize = maxdirentries * sizeof(struct sfs_dir); direntries = domalloc(dirsize); sortvector = domalloc(ndirentries * sizeof(int)); dirread(&sfi, direntries, ndirentries); for (i=ndirentries; i<maxdirentries; i++) { direntries[i].sfd_ino = SFS_NOINO; bzero(direntries[i].sfd_name, sizeof(direntries[i].sfd_name)); } for (i=0; i<ndirentries; i++) { if (check_dir_entry(pathsofar, i, &direntries[i])) { dchanged = 1; } sortvector[i] = i; } sortdir(sortvector, direntries, ndirentries); /* don't use ndirentries-1 here in case ndirentries == 0 */ for (i=0; i+1<ndirentries; i++) { struct sfs_dir *d1 = &direntries[sortvector[i]]; struct sfs_dir *d2 = &direntries[sortvector[i+1]]; assert(d1 != d2); if (d1->sfd_ino == SFS_NOINO) { continue; } if (!strcmp(d1->sfd_name, d2->sfd_name)) { if (d1->sfd_ino == d2->sfd_ino) { setbadness(EXIT_RECOV); warnx("Directory /%s: Duplicate entries for " "%s (merged)", pathsofar, d1->sfd_name); d1->sfd_ino = SFS_NOINO; d1->sfd_name[0] = 0; } else { snprintf(d1->sfd_name, sizeof(d1->sfd_name), "FSCK.%lu.%lu", (unsigned long) d1->sfd_ino, (unsigned long) uniquecounter++); setbadness(EXIT_RECOV); warnx("Directory /%s: Duplicate names %s " "(one renamed: %s)", pathsofar, d2->sfd_name, d1->sfd_name); } dchanged = 1; } } for (i=0; i<ndirentries; i++) { if (!strcmp(direntries[i].sfd_name, ".")) { if (direntries[i].sfd_ino != ino) { setbadness(EXIT_RECOV); warnx("Directory /%s: Incorrect `.' entry " "(fixed)", pathsofar); direntries[i].sfd_ino = ino; dchanged = 1; } assert(dotseen==0); /* due to duplicate checking */ dotseen = 1; } else if (!strcmp(direntries[i].sfd_name, "..")) { if (direntries[i].sfd_ino != parentino) { setbadness(EXIT_RECOV); warnx("Directory /%s: Incorrect `..' entry " "(fixed)", pathsofar); direntries[i].sfd_ino = parentino; dchanged = 1; } assert(dotdotseen==0); /* due to duplicate checking */ dotdotseen = 1; } } if (!dotseen) { if (dir_tryadd(direntries, ndirentries, ".", ino)==0) { setbadness(EXIT_RECOV); warnx("Directory /%s: No `.' entry (added)", pathsofar); dchanged = 1; } else if (dir_tryadd(direntries, maxdirentries, ".", ino)==0) { setbadness(EXIT_RECOV); warnx("Directory /%s: No `.' entry (added)", pathsofar); ndirentries++; dchanged = 1; sfi.sfi_size += sizeof(struct sfs_dir); ichanged = 1; } else { setbadness(EXIT_UNRECOV); warnx("Directory /%s: No `.' entry (NOT FIXED)", pathsofar); } } if (!dotdotseen) { if (dir_tryadd(direntries, ndirentries, "..", parentino)==0) { setbadness(EXIT_RECOV); warnx("Directory /%s: No `..' entry (added)", pathsofar); dchanged = 1; } else if (dir_tryadd(direntries, maxdirentries, "..", parentino)==0) { setbadness(EXIT_RECOV); warnx("Directory /%s: No `..' entry (added)", pathsofar); ndirentries++; dchanged = 1; sfi.sfi_size += sizeof(struct sfs_dir); ichanged = 1; } else { setbadness(EXIT_UNRECOV); warnx("Directory /%s: No `..' entry (NOT FIXED)", pathsofar); } } subdircount=0; for (i=0; i<ndirentries; i++) { if (!strcmp(direntries[i].sfd_name, ".")) { /* nothing */ } else if (!strcmp(direntries[i].sfd_name, "..")) { /* nothing */ } else if (direntries[i].sfd_ino == SFS_NOINO) { /* nothing */ } else { char path[strlen(pathsofar)+SFS_NAMELEN+1]; struct sfs_inode subsfi; diskread(&subsfi, direntries[i].sfd_ino); swapinode(&subsfi); snprintf(path, sizeof(path), "%s/%s", pathsofar, direntries[i].sfd_name); switch (subsfi.sfi_type) { case SFS_TYPE_FILE: if (check_inode_blocks(direntries[i].sfd_ino, &subsfi, 0)) { swapinode(&subsfi); diskwrite(&subsfi, direntries[i].sfd_ino); } observe_filelink(direntries[i].sfd_ino); break; case SFS_TYPE_DIR: if (check_dir(direntries[i].sfd_ino, ino, path)) { setbadness(EXIT_RECOV); warnx("Directory /%s: Crosslink to " "other directory (removed)", path); direntries[i].sfd_ino = SFS_NOINO; direntries[i].sfd_name[0] = 0; dchanged = 1; } else { subdircount++; } break; default: setbadness(EXIT_RECOV); warnx("Object /%s: Invalid inode type " "(removed)", path); direntries[i].sfd_ino = SFS_NOINO; direntries[i].sfd_name[0] = 0; dchanged = 1; break; } } } if (sfi.sfi_linkcount != subdircount+2) { setbadness(EXIT_RECOV); warnx("Directory /%s: Link count %lu should be %lu (fixed)", pathsofar, (unsigned long) sfi.sfi_linkcount, (unsigned long) subdircount+2); sfi.sfi_linkcount = subdircount+2; ichanged = 1; } if (dchanged) { dirwrite(&sfi, direntries, ndirentries); } if (ichanged) { swapinode(&sfi); diskwrite(&sfi, ino); } free(direntries); free(sortvector); return 0; }
/* * Process a directory. INO is the inode number; PARENTINO is the * parent's inode number; PATHSOFAR is the path to this directory. * * Recursively checks its subdirs. * * In the FUTURE we might want to improve the handling of crosslinked * directories so it picks the parent that the .. entry points to, * instead of the first entry we recursively find. Beware of course * that the .. entry might not point to anywhere valid at all... */ static int pass2_dir(uint32_t ino, uint32_t parentino, const char *pathsofar) { struct sfs_dinode sfi; struct sfs_dir *direntries; int *sortvector; uint32_t dirsize, ndirentries, maxdirentries, subdircount, i; int ichanged=0, dchanged=0, dotseen=0, dotdotseen=0; if (inode_visitdir(ino)) { /* crosslinked dir; tell parent to remove the entry */ return 1; } /* Load the inode. */ sfs_readinode(ino, &sfi); /* * Load the directory. If there is any leftover room in the * last block, allocate space for it in case we want to insert * entries. */ ndirentries = sfi.sfi_size/sizeof(struct sfs_dir); maxdirentries = SFS_ROUNDUP(ndirentries, SFS_BLOCKSIZE/sizeof(struct sfs_dir)); dirsize = maxdirentries * sizeof(struct sfs_dir); direntries = domalloc(dirsize); sortvector = domalloc(ndirentries * sizeof(int)); sfs_readdir(&sfi, direntries, ndirentries); for (i=ndirentries; i<maxdirentries; i++) { direntries[i].sfd_ino = SFS_NOINO; bzero(direntries[i].sfd_name, sizeof(direntries[i].sfd_name)); } /* * Sort by name and check for duplicate names. */ sfsdir_sort(direntries, ndirentries, sortvector); /* don't use ndirentries-1 here, in case ndirentries == 0 */ for (i=0; i+1<ndirentries; i++) { struct sfs_dir *d1 = &direntries[sortvector[i]]; struct sfs_dir *d2 = &direntries[sortvector[i+1]]; assert(d1 != d2); if (d1->sfd_ino == SFS_NOINO || d2->sfd_ino == SFS_NOINO) { /* sfsdir_sort puts these last */ continue; } if (!strcmp(d1->sfd_name, d2->sfd_name)) { if (d1->sfd_ino == d2->sfd_ino) { setbadness(EXIT_RECOV); warnx("Directory %s: Duplicate entries for " "%s (merged)", pathsofar, d1->sfd_name); d1->sfd_ino = SFS_NOINO; d1->sfd_name[0] = 0; } else { /* XXX: what if FSCK.n.m already exists? */ snprintf(d1->sfd_name, sizeof(d1->sfd_name), "FSCK.%lu.%lu", (unsigned long) d1->sfd_ino, (unsigned long) uniqueid()); setbadness(EXIT_RECOV); warnx("Directory %s: Duplicate names %s " "(one renamed: %s)", pathsofar, d2->sfd_name, d1->sfd_name); } dchanged = 1; } } /* * Look for the . and .. entries. */ for (i=0; i<ndirentries; i++) { if (!strcmp(direntries[i].sfd_name, ".")) { if (direntries[i].sfd_ino != ino) { setbadness(EXIT_RECOV); warnx("Directory %s: Incorrect `.' entry " "(fixed)", pathsofar); direntries[i].sfd_ino = ino; dchanged = 1; } /* duplicates are checked above -> only one . here */ assert(dotseen==0); dotseen = 1; } else if (!strcmp(direntries[i].sfd_name, "..")) { if (direntries[i].sfd_ino != parentino) { setbadness(EXIT_RECOV); warnx("Directory %s: Incorrect `..' entry " "(fixed)", pathsofar); direntries[i].sfd_ino = parentino; dchanged = 1; } /* duplicates are checked above -> only one .. here */ assert(dotdotseen==0); dotdotseen = 1; } } /* * If no . entry, try to insert one. */ if (!dotseen) { if (sfsdir_tryadd(direntries, ndirentries, ".", ino)==0) { setbadness(EXIT_RECOV); warnx("Directory %s: No `.' entry (added)", pathsofar); dchanged = 1; } else if (sfsdir_tryadd(direntries, maxdirentries, ".", ino)==0) { setbadness(EXIT_RECOV); warnx("Directory %s: No `.' entry (added)", pathsofar); ndirentries++; dchanged = 1; sfi.sfi_size += sizeof(struct sfs_dir); ichanged = 1; } else { setbadness(EXIT_UNRECOV); warnx("Directory %s: No `.' entry (NOT FIXED)", pathsofar); } } /* * If no .. entry, try to insert one. */ if (!dotdotseen) { if (sfsdir_tryadd(direntries, ndirentries, "..", parentino)==0) { setbadness(EXIT_RECOV); warnx("Directory %s: No `..' entry (added)", pathsofar); dchanged = 1; } else if (sfsdir_tryadd(direntries, maxdirentries, "..", parentino)==0) { setbadness(EXIT_RECOV); warnx("Directory %s: No `..' entry (added)", pathsofar); ndirentries++; dchanged = 1; sfi.sfi_size += sizeof(struct sfs_dir); ichanged = 1; } else { setbadness(EXIT_UNRECOV); warnx("Directory %s: No `..' entry (NOT FIXED)", pathsofar); } } /* * Now load each inode in the directory. * * For regular files, count the number of links we see; for * directories, recurse. Count the number of subdirs seen * so we can correct our own link count if necessary. */ subdircount=0; for (i=0; i<ndirentries; i++) { if (direntries[i].sfd_ino == SFS_NOINO) { /* nothing */ } else if (!strcmp(direntries[i].sfd_name, ".")) { /* nothing */ } else if (!strcmp(direntries[i].sfd_name, "..")) { /* nothing */ } else { char path[strlen(pathsofar)+SFS_NAMELEN+1]; struct sfs_dinode subsfi; sfs_readinode(direntries[i].sfd_ino, &subsfi); snprintf(path, sizeof(path), "%s/%s", pathsofar, direntries[i].sfd_name); switch (subsfi.sfi_type) { case SFS_TYPE_FILE: inode_addlink(direntries[i].sfd_ino); break; case SFS_TYPE_DIR: if (pass2_dir(direntries[i].sfd_ino, ino, path)) { setbadness(EXIT_RECOV); warnx("Directory %s: Crosslink to " "other directory (removed)", path); direntries[i].sfd_ino = SFS_NOINO; direntries[i].sfd_name[0] = 0; dchanged = 1; } else { subdircount++; } break; default: setbadness(EXIT_RECOV); warnx("Object %s: Invalid inode type " "(removed)", path); direntries[i].sfd_ino = SFS_NOINO; direntries[i].sfd_name[0] = 0; dchanged = 1; break; } } } /* * Fix up the link count if needed. */ if (sfi.sfi_linkcount != subdircount+2) { setbadness(EXIT_RECOV); warnx("Directory %s: Link count %lu should be %lu (fixed)", pathsofar, (unsigned long) sfi.sfi_linkcount, (unsigned long) subdircount+2); sfi.sfi_linkcount = subdircount+2; ichanged = 1; } /* * Write back anything that changed, clean up, and return. */ if (dchanged) { sfs_writedir(&sfi, direntries, ndirentries); } if (ichanged) { sfs_writeinode(ino, &sfi); } free(direntries); free(sortvector); return 0; }
/* * Traverse an indirect block, recording blocks that are in use, * dropping any entries that are past EOF, and clearing any entries * that point outside the volume. * * XXX: this should be extended to be able to recover from crosslinked * blocks. Currently it just complains in freemap.c and sets * EXIT_UNRECOV. * * The traversal is recursive; the state is maintained in IBS (as * described above). IENTRY is a pointer to the entry in the parent * indirect block (or the inode) that names the block we're currently * scanning. IECHANGEDP should be set to 1 if *IENTRY is changed. * INDIRECTION is the indirection level of this block (1, 2, or 3). */ static void check_indirect_block(struct ibstate *ibs, uint32_t *ientry, int *iechangedp, int indirection) { uint32_t entries[SFS_DBPERIDB]; uint32_t i, ct; uint32_t coveredblocks; int localchanged = 0; int j; if (*ientry > 0 && *ientry < ibs->volblocks) { sfs_readindirect(*ientry, entries); freemap_blockinuse(*ientry, B_IBLOCK, ibs->ino); } else { if (*ientry >= ibs->volblocks) { setbadness(EXIT_RECOV); warnx("Inode %lu: indirect block pointer (level %d) " "for block %lu outside of volume: %lu " "(cleared)\n", (unsigned long)ibs->ino, indirection, (unsigned long)ibs->curfileblock, (unsigned long)*ientry); *ientry = 0; *iechangedp = 1; } coveredblocks = 1; for (j=0; j<indirection; j++) { coveredblocks *= SFS_DBPERIDB; } ibs->curfileblock += coveredblocks; return; } if (indirection > 1) { for (i=0; i<SFS_DBPERIDB; i++) { check_indirect_block(ibs, &entries[i], &localchanged, indirection-1); } } else { assert(indirection==1); for (i=0; i<SFS_DBPERIDB; i++) { if (entries[i] >= ibs->volblocks) { setbadness(EXIT_RECOV); warnx("Inode %lu: direct block pointer for " "block %lu outside of volume: %lu " "(cleared)\n", (unsigned long)ibs->ino, (unsigned long)ibs->curfileblock, (unsigned long)entries[i]); entries[i] = 0; localchanged = 1; } else if (entries[i] != 0) { if (ibs->curfileblock < ibs->fileblocks) { freemap_blockinuse(entries[i], ibs->usagetype, ibs->ino); } else { setbadness(EXIT_RECOV); ibs->pasteofcount++; freemap_blockfree(entries[i]); entries[i] = 0; localchanged = 1; } } ibs->curfileblock++; } } ct=0; for (i=ct=0; i<SFS_DBPERIDB; i++) { if (entries[i]!=0) ct++; } if (ct==0) { if (*ientry != 0) { setbadness(EXIT_RECOV); /* this is not necessarily correct */ /*ibs->pasteofcount++;*/ *iechangedp = 1; freemap_blockfree(*ientry); *ientry = 0; } } else { assert(*ientry != 0); if (localchanged) { sfs_writeindirect(*ientry, entries); } } }