/* * Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2009, 2013 * The President and Fellows of Harvard College. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include "compat.h" #include #include "disk.h" #include "utils.h" #include "ibmacros.h" #include "sfs.h" #include "sb.h" #include "freemap.h" #include "inode.h" #include "passes.h" #include "main.h" static unsigned long count_dirs=0, count_files=0; /* * State for checking indirect blocks. */ struct ibstate { uint32_t ino; /* inode we're doing (constant) */ uint32_t curfileblock; /* current block offset in the file */ uint32_t fileblocks; /* file size in blocks (constant) */ uint32_t volblocks; /* volume size in blocks (constant) */ unsigned pasteofcount; /* number of blocks found past eof */ blockusage_t usagetype; /* how to call freemap_blockinuse() */ }; /* * 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; jcurfileblock += coveredblocks; return; } if (indirection > 1) { for (i=0; 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; ipasteofcount++;*/ *iechangedp = 1; freemap_blockfree(*ientry); *ientry = 0; } } else { assert(*ientry != 0); if (localchanged) { sfs_writeindirect(*ientry, entries); } } } /* * 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= 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)datablock); SET_D(sfi, ibs.curfileblock) = 0; changed = 1; } else if (datablock > 0) { if (ibs.curfileblock < ibs.fileblocks) { freemap_blockinuse(datablock, ibs.usagetype, ibs.ino); } else { setbadness(EXIT_RECOV); ibs.pasteofcount++; changed = 1; freemap_blockfree(datablock); SET_D(sfi, ibs.curfileblock) = 0; } } } for (i=0; i 0) { warnx("Inode %lu: %u blocks after EOF (freed)", (unsigned long) ibs.ino, ibs.pasteofcount); setbadness(EXIT_RECOV); } return changed; } /* * 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; } freemap_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; } /* * 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_direntry *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; } /* * 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_direntry *direntries; uint32_t ndirentries, i; int ichanged=0, dchanged=0; sfs_readinode(ino, &sfi); if (sfi.sfi_size % sizeof(struct sfs_direntry) != 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_direntry)); 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_direntry); direntries = domalloc(sfi.sfi_size); sfs_readdir(&sfi, direntries, ndirentries); for (i=0; i