Initial Spring 2016 commit.
This commit is contained in:
496
userland/sbin/sfsck/pass1.c
Normal file
496
userland/sbin/sfsck/pass1.c
Normal file
@@ -0,0 +1,496 @@
|
||||
/*
|
||||
* 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 <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <err.h>
|
||||
|
||||
#include "compat.h"
|
||||
#include <kern/sfs.h>
|
||||
|
||||
#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; 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
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<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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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<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 %u "
|
||||
"(removed)", path, subsfi.sfi_type);
|
||||
direntries[i].sfd_ino = SFS_NOINO;
|
||||
direntries[i].sfd_name[0] = 0;
|
||||
dchanged = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dchanged) {
|
||||
sfs_writedir(&sfi, direntries, ndirentries);
|
||||
}
|
||||
|
||||
free(direntries);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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_ROOTDIR_INO, &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_ROOTDIR_INO, &sfi);
|
||||
break;
|
||||
}
|
||||
|
||||
snprintf(path, sizeof(path), "%s:", sb_volname());
|
||||
pass1_dir(SFS_ROOTDIR_INO, path);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// public interface
|
||||
|
||||
void
|
||||
pass1(void)
|
||||
{
|
||||
pass1_rootdir();
|
||||
}
|
||||
|
||||
unsigned long
|
||||
pass1_founddirs(void)
|
||||
{
|
||||
return count_dirs;
|
||||
}
|
||||
|
||||
unsigned long
|
||||
pass1_foundfiles(void)
|
||||
{
|
||||
return count_files;
|
||||
}
|
Reference in New Issue
Block a user