1019 lines
30 KiB
C
1019 lines
30 KiB
C
/*
|
|
* Check 2.xBSD filesystem.
|
|
*
|
|
* Copyright (C) 2006-2014 Serge Vakulenko, <serge@vak.ru>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software
|
|
* and its documentation for any purpose and without fee is hereby
|
|
* granted, provided that the above copyright notice appear in all
|
|
* copies and that both that the copyright notice and this
|
|
* permission notice and warranty disclaimer appear in supporting
|
|
* documentation, and that the name of the author not be used in
|
|
* advertising or publicity pertaining to distribution of the
|
|
* software without specific, written prior permission.
|
|
*
|
|
* The author disclaim all warranties with regard to this
|
|
* software, including all implied warranties of merchantability
|
|
* and fitness. In no event shall the author be liable for any
|
|
* special, indirect or consequential damages or any damages
|
|
* whatsoever resulting from loss of use, data or profits, whether
|
|
* in an action of contract, negligence or other tortious action,
|
|
* arising out of or in connection with the use or performance of
|
|
* this software.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include "bsdfs.h"
|
|
|
|
extern int verbose;
|
|
|
|
#define MAXDUP 10 /* limit on dup blks (per inode) */
|
|
#define MAXBAD 10 /* limit on bad blks (per inode) */
|
|
#define DUP_LIST_SIZE 100 /* num of dup blocks to remember */
|
|
#define LINK_LIST_SIZE 20 /* num zero link cnts to remember */
|
|
|
|
#define STATE_BITS 2 /* bits per inode state */
|
|
#define STATE_MASK 3 /* mask for inode state */
|
|
#define STATES_PER_BYTE 4 /* inode states per byte */
|
|
#define USTATE 0 /* inode not allocated */
|
|
#define FSTATE 1 /* inode is file */
|
|
#define DSTATE 2 /* inode is directory */
|
|
#define CLEAR 3 /* inode is to be cleared */
|
|
|
|
#define DATA 1 /* flags for scan_inode() */
|
|
#define ADDR 0
|
|
|
|
#define ALTERD 010 /* values returned by scan functions */
|
|
#define KEEPON 004
|
|
#define SKIP 002
|
|
#define STOP 001
|
|
|
|
#define outrange(fs,x) ((x) < (fs)->isize || (x) >= (fs)->fsize)
|
|
|
|
/* block scan function, called by scan_inode for every file block */
|
|
typedef int scanner_t (fs_inode_t *inode, unsigned blk, void *arg);
|
|
|
|
static unsigned char buf_data [BSDFS_BSIZE]; /* buffer data for scan_directory */
|
|
static unsigned buf_bno; /* buffer block number */
|
|
static int buf_dirty; /* buffer data modified */
|
|
|
|
static unsigned dup_list [DUP_LIST_SIZE]; /* dup block table */
|
|
static unsigned *dup_end; /* next entry in dup table */
|
|
static unsigned *dup_multi; /* multiple dups part of table */
|
|
|
|
static unsigned bad_link_list [LINK_LIST_SIZE]; /* inos with zero link cnts */
|
|
static unsigned *bad_link_end; /* next entry in table */
|
|
|
|
static char *block_map; /* primary blk allocation map */
|
|
static char *free_map; /* secondary blk allocation map */
|
|
static char *state_map; /* inode state table */
|
|
static int *link_count; /* link count table */
|
|
|
|
static char pathname [256]; /* file path name for pass2 */
|
|
static char *pathp; /* pointer to pathname position */
|
|
static char *thisname; /* ptr to current pathname component */
|
|
|
|
static char *lost_found_name = "lost+found";
|
|
static unsigned lost_found_inode; /* lost & found directory */
|
|
|
|
static int free_list_corrupted; /* corrupted free list */
|
|
static int bad_blocks; /* num of bad blks seen (per inode) */
|
|
static int dup_blocks; /* num of dup blks seen (per inode) */
|
|
|
|
static char *find_inode_name; /* searching for this name */
|
|
static unsigned find_inode_result; /* result of inode search */
|
|
static unsigned lost_inode; /* lost file to reconnect */
|
|
|
|
static unsigned long scan_filesize; /* file size, decremented during scan */
|
|
static unsigned total_files; /* number of files seen */
|
|
|
|
static void set_inode_state (unsigned inum, int s)
|
|
{
|
|
unsigned int byte, shift;
|
|
|
|
byte = inum / STATES_PER_BYTE;
|
|
shift = inum % STATES_PER_BYTE * STATE_BITS;
|
|
state_map [byte] &= ~(STATE_MASK << shift);
|
|
state_map [byte] |= s << shift;
|
|
}
|
|
|
|
static int inode_state (unsigned inum)
|
|
{
|
|
unsigned int byte, shift;
|
|
|
|
byte = inum / STATES_PER_BYTE;
|
|
shift = inum % STATES_PER_BYTE * STATE_BITS;
|
|
return (state_map [byte] >> shift) & STATE_MASK;
|
|
}
|
|
|
|
static int block_is_busy (unsigned blk)
|
|
{
|
|
return block_map [blk >> 3] & (1 << (blk & 7));
|
|
}
|
|
|
|
static void mark_block_busy (unsigned blk)
|
|
{
|
|
block_map [blk >> 3] |= 1 << (blk & 7);
|
|
}
|
|
|
|
static void mark_block_free (unsigned blk)
|
|
{
|
|
block_map [blk >> 3] &= ~(1 << (blk & 7));
|
|
}
|
|
|
|
static void mark_free_list (unsigned blk)
|
|
{
|
|
free_map [blk >> 3] |= 1 << (blk & 7);
|
|
}
|
|
|
|
static int in_free_list (unsigned blk)
|
|
{
|
|
return free_map [blk >> 3] & (1 << (blk & 7));
|
|
}
|
|
|
|
static void print_io_error (char *s, unsigned blk)
|
|
{
|
|
printf ("\nCAN NOT %s: BLK %d\n", s, blk);
|
|
}
|
|
|
|
static void buf_flush (fs_t *fs)
|
|
{
|
|
if (buf_dirty && fs->writable) {
|
|
/*printf ("WRITE blk %d\n", buf_bno);*/
|
|
if (! fs_write_block (fs, buf_bno, buf_data))
|
|
print_io_error ("WRITE", buf_bno);
|
|
}
|
|
buf_dirty = 0;
|
|
}
|
|
|
|
static int buf_get (fs_t *fs, unsigned blk)
|
|
{
|
|
if (buf_bno == blk)
|
|
return 1;
|
|
buf_flush (fs);
|
|
/*printf ("read blk %d\n", blk);*/
|
|
if (! fs_read_block (fs, blk, buf_data)) {
|
|
print_io_error ("READ", blk);
|
|
buf_bno = (unsigned)-1;
|
|
return 0;
|
|
}
|
|
buf_bno = blk;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Scan recursively the indirect block of the inode,
|
|
* and for every block call the given function.
|
|
*/
|
|
static int scan_indirect_block (fs_inode_t *inode, unsigned blk,
|
|
int double_indirect, int flg, scanner_t *func, void *arg)
|
|
{
|
|
unsigned nb;
|
|
int ret, i;
|
|
unsigned char data [BSDFS_BSIZE];
|
|
|
|
/*printf ("check %siblock %d: \n", double_indirect > 1 ? "triple " : double_indirect ? "double " : "", blk);*/
|
|
if (flg == ADDR) {
|
|
ret = (*func) (inode, blk, arg);
|
|
if (! (ret & KEEPON))
|
|
return ret;
|
|
}
|
|
if (outrange (inode->fs, blk)) /* protect thyself */
|
|
return SKIP;
|
|
|
|
if (! fs_read_block (inode->fs, blk, data)) {
|
|
print_io_error ("READ", blk);
|
|
return SKIP;
|
|
}
|
|
for (i = 0; i < BSDFS_BSIZE; i+=2) {
|
|
nb = data [i+1] << 8 | data [i];
|
|
if (nb) {
|
|
if (double_indirect)
|
|
ret = scan_indirect_block (inode, nb,
|
|
double_indirect - 1, flg, func, arg);
|
|
else
|
|
ret = (*func) (inode, nb, arg);
|
|
|
|
if (ret & STOP)
|
|
return ret;
|
|
}
|
|
}
|
|
return KEEPON;
|
|
}
|
|
|
|
/*
|
|
* Scan recursively the block list of the inode,
|
|
* and for every block call the given function.
|
|
* Option flg:
|
|
* - when ADDR - call func for both data and indirect blocks
|
|
* - when DATA - only data blocks are processed
|
|
*/
|
|
static int scan_inode (fs_inode_t *inode, int flg, scanner_t *func, void *arg)
|
|
{
|
|
unsigned *ap;
|
|
int ret;
|
|
|
|
/*printf ("check inode %d: %#o\n", inode->number, inode->mode);*/
|
|
if (((inode->mode & INODE_MODE_FMT) == INODE_MODE_FBLK) ||
|
|
((inode->mode & INODE_MODE_FMT) == INODE_MODE_FCHR))
|
|
return KEEPON;
|
|
scan_filesize = inode->size;
|
|
|
|
/* Check direct blocks. */
|
|
for (ap = inode->addr; ap < &inode->addr[NDADDR]; ap++) {
|
|
if (*ap) {
|
|
ret = (*func) (inode, *ap, arg);
|
|
if (ret & STOP)
|
|
return ret;
|
|
}
|
|
}
|
|
if (inode->addr[NADDR-3]) {
|
|
/* Check the indirect block. */
|
|
ret = scan_indirect_block (inode, inode->addr[NADDR-3], 0,
|
|
flg, func, arg);
|
|
if (ret & STOP)
|
|
return (ret);
|
|
}
|
|
if (inode->addr[NADDR-2]) {
|
|
/* Check the double indirect block. */
|
|
ret = scan_indirect_block (inode, inode->addr[NADDR-2], 1,
|
|
flg, func, arg);
|
|
if (ret & STOP)
|
|
return (ret);
|
|
}
|
|
if (inode->addr[NADDR-1]) {
|
|
/* Check the last (triple indirect) block. */
|
|
ret = scan_indirect_block (inode, inode->addr[NADDR-1], 2,
|
|
flg, func, arg);
|
|
if (ret & STOP)
|
|
return (ret);
|
|
}
|
|
return KEEPON;
|
|
}
|
|
|
|
static void print_block_error (char *s, unsigned blk, unsigned inum)
|
|
{
|
|
printf ("%u %s I=%u\n", blk, s, inum);
|
|
}
|
|
|
|
/*
|
|
* Called once for every block of every file.
|
|
* Mark blocks as busy on block map.
|
|
* If duplicates are found, put them into dup_list.
|
|
*/
|
|
static int pass1 (fs_inode_t *inode, unsigned blk, void *arg)
|
|
{
|
|
unsigned *dlp;
|
|
unsigned *blocks = arg;
|
|
|
|
/*printf ("pass1 inode %d block %d: \n", inode->number, blk);*/
|
|
if (outrange (inode->fs, blk)) {
|
|
print_block_error ("BAD", blk, inode->number);
|
|
set_inode_state (inode->number, CLEAR); /* mark for possible clearing */
|
|
if (++bad_blocks >= MAXBAD) {
|
|
printf ("EXCESSIVE BAD BLKS I=%u\n", inode->number);
|
|
return STOP;
|
|
}
|
|
return SKIP;
|
|
}
|
|
if (block_is_busy (blk)) {
|
|
print_block_error ("DUP", blk, inode->number);
|
|
set_inode_state (inode->number, CLEAR); /* mark for possible clearing */
|
|
if (++dup_blocks >= MAXDUP) {
|
|
printf ("EXCESSIVE DUP BLKS I=%u\n", inode->number);
|
|
return STOP;
|
|
}
|
|
if (dup_end >= &dup_list[DUP_LIST_SIZE]) {
|
|
printf ("DUP TABLE OVERFLOW.\n");
|
|
return STOP;
|
|
}
|
|
for (dlp = dup_list; dlp < dup_multi; dlp++) {
|
|
if (*dlp == blk) {
|
|
*dup_end++ = blk;
|
|
break;
|
|
}
|
|
}
|
|
if (dlp >= dup_multi) {
|
|
*dup_end++ = *dup_multi;
|
|
*dup_multi++ = blk;
|
|
}
|
|
} else {
|
|
if (blocks)
|
|
++*blocks;
|
|
mark_block_busy (blk);
|
|
}
|
|
return KEEPON;
|
|
}
|
|
|
|
static int pass1b (fs_inode_t *inode, unsigned blk, void *arg)
|
|
{
|
|
unsigned *dlp;
|
|
|
|
if (outrange (inode->fs, blk))
|
|
return SKIP;
|
|
for (dlp = dup_list; dlp < dup_multi; dlp++) {
|
|
if (*dlp == blk) {
|
|
print_block_error ("DUP", blk, inode->number);
|
|
set_inode_state (inode->number, CLEAR); /* mark for possible clearing */
|
|
*dlp = *--dup_multi;
|
|
*dup_multi = blk;
|
|
return (dup_multi == dup_list ? STOP : KEEPON);
|
|
}
|
|
}
|
|
return KEEPON;
|
|
}
|
|
|
|
/*
|
|
* Read directory, and for every entry call given function.
|
|
* If function altered the contents of entry, then write it back.
|
|
*/
|
|
static int scan_directory (fs_inode_t *inode, unsigned blk, void *arg)
|
|
{
|
|
fs_dirent_t direntry;
|
|
unsigned char *dirp;
|
|
int (*func) () = arg;
|
|
int n;
|
|
|
|
/*printf ("scan_directory: I=%d, blk=%d\n", inode->number, blk);*/
|
|
if (outrange (inode->fs, blk)) {
|
|
scan_filesize -= BSDFS_BSIZE;
|
|
return SKIP;
|
|
}
|
|
dirp = buf_data;
|
|
while (dirp < &buf_data[BSDFS_BSIZE] && scan_filesize > 0) {
|
|
if (! buf_get (inode->fs, blk)) {
|
|
scan_filesize -= (&buf_data[BSDFS_BSIZE] - dirp);
|
|
return SKIP;
|
|
}
|
|
fs_dirent_unpack (&direntry, dirp);
|
|
if (direntry.reclen == 0)
|
|
break;
|
|
|
|
/* For every directory entry, call handler. */
|
|
n = (*func) (inode->fs, &direntry);
|
|
|
|
if (n & ALTERD) {
|
|
if (buf_get (inode->fs, blk)) {
|
|
fs_dirent_pack (dirp, &direntry);
|
|
buf_dirty = 1;
|
|
} else
|
|
n &= ~ALTERD;
|
|
}
|
|
if (n & STOP)
|
|
return n;
|
|
dirp += direntry.reclen;
|
|
scan_filesize -= direntry.reclen;
|
|
}
|
|
return (scan_filesize > 0) ? KEEPON : STOP;
|
|
}
|
|
|
|
static void print_inode (fs_inode_t *inode)
|
|
{
|
|
char *p;
|
|
|
|
printf (" I=%u ", inode->number);
|
|
printf (" OWNER=%d ", inode->uid);
|
|
printf ("MODE=%o\n", inode->mode);
|
|
printf ("SIZE=%ld ", inode->size);
|
|
p = ctime (&inode->mtime);
|
|
printf ("MTIME=%12.12s %4.4s\n", p+4, p+20);
|
|
}
|
|
|
|
static void print_dir_error (fs_t *fs, unsigned inum, char *s)
|
|
{
|
|
fs_inode_t inode;
|
|
|
|
if (! fs_inode_get (fs, &inode, inum)) {
|
|
printf ("%s I=%u\nNAME=%s\n", s, inum, pathname);
|
|
return;
|
|
}
|
|
printf ("%s ", s);
|
|
print_inode (&inode);
|
|
printf ("%s=%s\n", ((inode.mode & INODE_MODE_FMT) ==
|
|
INODE_MODE_FDIR) ? "DIR" : "FILE", pathname);
|
|
}
|
|
|
|
static void scan_pass2 (fs_t *fs, unsigned inum);
|
|
|
|
/*
|
|
* Clear directory entries which refer to duplicated or unallocated inodes.
|
|
* Decrement link counters.
|
|
*/
|
|
static int pass2 (fs_t *fs, fs_dirent_t *dirp)
|
|
{
|
|
int inum, ret = KEEPON;
|
|
fs_inode_t inode;
|
|
|
|
inum = dirp->ino;
|
|
if (inum == 0)
|
|
return KEEPON;
|
|
|
|
/* Copy file name from dirp to pathp */
|
|
thisname = pathp;
|
|
strcpy (pathp, dirp->name);
|
|
pathp += strlen (pathp);
|
|
/*printf ("%s %d\n", pathname, inum);*/
|
|
if (inum > (fs->isize - 1) * BSDFS_INODES_PER_BLOCK ||
|
|
inum < BSDFS_ROOT_INODE)
|
|
print_dir_error (fs, inum, "I OUT OF RANGE");
|
|
else {
|
|
again: switch (inode_state (inum)) {
|
|
case USTATE:
|
|
print_dir_error (fs, inum, "UNALLOCATED");
|
|
if (fs->writable) {
|
|
dirp->ino = 0;
|
|
ret |= ALTERD;
|
|
break;
|
|
}
|
|
break;
|
|
case CLEAR:
|
|
print_dir_error (fs, inum, "DUP/BAD");
|
|
if (fs->writable) {
|
|
dirp->ino = 0;
|
|
ret |= ALTERD;
|
|
break;
|
|
}
|
|
if (! fs_inode_get (fs, &inode, inum))
|
|
break;
|
|
set_inode_state (inum, ((inode.mode & INODE_MODE_FMT) ==
|
|
INODE_MODE_FDIR) ? DSTATE : FSTATE);
|
|
goto again;
|
|
case FSTATE:
|
|
--link_count [inum];
|
|
break;
|
|
case DSTATE:
|
|
--link_count [inum];
|
|
scan_pass2 (fs, inum);
|
|
}
|
|
}
|
|
pathp = thisname;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Traverse directory tree. Call pass2 for every directory entry.
|
|
* Keep current file name in 'pathname'.
|
|
*/
|
|
static void scan_pass2 (fs_t *fs, unsigned inum)
|
|
{
|
|
fs_inode_t inode;
|
|
char *savname;
|
|
unsigned long savsize;
|
|
|
|
set_inode_state (inum, FSTATE);
|
|
if (! fs_inode_get (fs, &inode, inum))
|
|
return;
|
|
*pathp++ = '/';
|
|
savname = thisname;
|
|
savsize = scan_filesize;
|
|
scan_inode (&inode, DATA, scan_directory, pass2);
|
|
scan_filesize = savsize;
|
|
thisname = savname;
|
|
*--pathp = 0;
|
|
}
|
|
|
|
/*
|
|
* Find inode number by name.
|
|
* The name is in 'find_inode_name'.
|
|
* Put resulting inode number into 'find_inode_result'.
|
|
*/
|
|
static int find_inode (fs_t *fs, fs_dirent_t *dirp)
|
|
{
|
|
if (dirp->ino == 0)
|
|
return KEEPON;
|
|
if (strcmp (find_inode_name, dirp->name) == 0) {
|
|
if (dirp->ino >= BSDFS_ROOT_INODE &&
|
|
dirp->ino < (fs->isize-1) * BSDFS_INODES_PER_BLOCK)
|
|
find_inode_result = dirp->ino;
|
|
return STOP;
|
|
}
|
|
return KEEPON;
|
|
}
|
|
|
|
/*
|
|
* Find a free slot and make link to 'lost_inode'.
|
|
* Create filename of a kind "#01234".
|
|
*/
|
|
static int make_lost_entry (fs_t *fs, fs_dirent_t *dirp)
|
|
{
|
|
if (dirp->ino)
|
|
return KEEPON;
|
|
dirp->ino = lost_inode;
|
|
sprintf (dirp->name, "#%05d", dirp->ino);
|
|
return ALTERD | STOP;
|
|
}
|
|
|
|
/*
|
|
* For entry ".." set inode number to 'lost_found_inode'.
|
|
*/
|
|
static int dotdot_to_lost_found (fs_t *fs, fs_dirent_t *dirp)
|
|
{
|
|
if (dirp->name[0] == '.' && dirp->name[1] == '.' &&
|
|
dirp->name[2] == 0) {
|
|
dirp->ino = lost_found_inode;
|
|
return ALTERD | STOP;
|
|
}
|
|
return KEEPON;
|
|
}
|
|
|
|
/*
|
|
* Return lost+found inode number.
|
|
* TODO: create /lost+found when not available.
|
|
*/
|
|
static unsigned find_lost_found (fs_t *fs)
|
|
{
|
|
fs_inode_t root;
|
|
|
|
/* Find lost_found directory inode number. */
|
|
if (! fs_inode_get (fs, &root, BSDFS_ROOT_INODE))
|
|
return 0;
|
|
find_inode_name = lost_found_name;
|
|
find_inode_result = 0;
|
|
scan_inode (&root, DATA, scan_directory, find_inode);
|
|
return find_inode_result;
|
|
}
|
|
|
|
/*
|
|
* Restore a link to parent directory - "..".
|
|
*/
|
|
static int move_to_lost_found (fs_inode_t *inode)
|
|
{
|
|
fs_inode_t lost_found;
|
|
|
|
printf ("UNREF %s ", ((inode->mode & INODE_MODE_FMT) ==
|
|
INODE_MODE_FDIR) ? "DIR" : "FILE");
|
|
print_inode (inode);
|
|
if (! inode->fs->writable)
|
|
return 0;
|
|
|
|
/* Get lost+found inode. */
|
|
if (lost_found_inode == 0) {
|
|
/* Find lost_found directory inode number. */
|
|
lost_found_inode = find_lost_found (inode->fs);
|
|
if (! lost_found_inode) {
|
|
printf ("SORRY. NO lost+found DIRECTORY\n\n");
|
|
return 0;
|
|
}
|
|
}
|
|
if (! fs_inode_get (inode->fs, &lost_found, lost_found_inode) ||
|
|
((lost_found.mode & INODE_MODE_FMT) != INODE_MODE_FDIR) ||
|
|
inode_state (lost_found_inode) != FSTATE) {
|
|
printf ("SORRY. NO lost+found DIRECTORY\n\n");
|
|
return 0;
|
|
}
|
|
if (lost_found.size % BSDFS_BSIZE) {
|
|
lost_found.size = (lost_found.size + BSDFS_BSIZE - 1) /
|
|
BSDFS_BSIZE * BSDFS_BSIZE;
|
|
if (! fs_inode_save (&lost_found, 1)) {
|
|
printf ("SORRY. ERROR WRITING lost+found I-NODE\n\n");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Put a file to lost+found. */
|
|
lost_inode = inode->number;
|
|
if ((scan_inode (&lost_found, DATA, scan_directory, make_lost_entry) &
|
|
ALTERD) == 0) {
|
|
printf ("SORRY. NO SPACE IN lost+found DIRECTORY\n\n");
|
|
return 0;
|
|
}
|
|
--link_count [inode->number];
|
|
|
|
if ((inode->mode & INODE_MODE_FMT) == INODE_MODE_FDIR) {
|
|
/* For ".." set inode number to lost_found_inode. */
|
|
scan_inode (inode, DATA, scan_directory, dotdot_to_lost_found);
|
|
if (fs_inode_get (inode->fs, &lost_found, lost_found_inode)) {
|
|
lost_found.nlink++;
|
|
++link_count [lost_found.number];
|
|
if (! fs_inode_save (&lost_found, 1)) {
|
|
printf ("SORRY. ERROR WRITING lost+found I-NODE\n\n");
|
|
return 0;
|
|
}
|
|
}
|
|
printf ("DIR I=%u CONNECTED.\n\n", inode->number);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Mark the block as free. Remove it from dup list.
|
|
*/
|
|
static int pass4 (fs_inode_t *inode, unsigned blk, void *arg)
|
|
{
|
|
unsigned *dlp;
|
|
unsigned *blocks = arg;
|
|
|
|
if (outrange (inode->fs, blk))
|
|
return SKIP;
|
|
if (block_is_busy (blk)) {
|
|
/* Free block. */
|
|
for (dlp = dup_list; dlp < dup_end; dlp++)
|
|
if (*dlp == blk) {
|
|
*dlp = *--dup_end;
|
|
return KEEPON;
|
|
}
|
|
mark_block_free (blk);
|
|
if (blocks)
|
|
--*blocks;
|
|
}
|
|
return KEEPON;
|
|
}
|
|
|
|
/*
|
|
* Clear the inode, mark it's blocks as free.
|
|
*/
|
|
static void clear_inode (fs_t *fs, unsigned inum, char *msg)
|
|
{
|
|
fs_inode_t inode;
|
|
|
|
if (! fs_inode_get (fs, &inode, inum))
|
|
return;
|
|
if (msg) {
|
|
printf ("%s %s", msg, ((inode.mode & INODE_MODE_FMT) ==
|
|
INODE_MODE_FDIR) ? "DIR" : "FILE");
|
|
print_inode (&inode);
|
|
}
|
|
if (fs->writable) {
|
|
total_files--;
|
|
scan_inode (&inode, ADDR, pass4, 0);
|
|
fs_inode_clear (&inode);
|
|
fs_inode_save (&inode, 1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Fix the link count of the inode.
|
|
* If no links - move it to lost+found.
|
|
*/
|
|
static void adjust_link_count (fs_t *fs, unsigned inum, unsigned lcnt)
|
|
{
|
|
fs_inode_t inode;
|
|
|
|
if (! fs_inode_get (fs, &inode, inum))
|
|
return;
|
|
if (inode.nlink == lcnt) {
|
|
/* No links to file - move to lost+found. */
|
|
if (! move_to_lost_found (&inode))
|
|
clear_inode (fs, inum, 0);
|
|
} else {
|
|
printf ("LINK COUNT %s", (lost_found_inode==inum) ? lost_found_name :
|
|
(((inode.mode & INODE_MODE_FMT) == INODE_MODE_FDIR) ?
|
|
"DIR" : "FILE"));
|
|
print_inode (&inode);
|
|
printf ("COUNT %d SHOULD BE %d\n",
|
|
inode.nlink, inode.nlink - lcnt);
|
|
if (fs->writable) {
|
|
inode.nlink -= lcnt;
|
|
fs_inode_save (&inode, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Called from check_free_list() for every block in free list.
|
|
* Count free blocks, detect duplicates.
|
|
*/
|
|
static int pass5 (fs_t *fs, unsigned blk, unsigned *free_blocks)
|
|
{
|
|
if (outrange (fs, blk)) {
|
|
free_list_corrupted = 1;
|
|
if (++bad_blocks >= MAXBAD) {
|
|
printf ("EXCESSIVE BAD BLKS IN FREE LIST.\n");
|
|
return STOP;
|
|
}
|
|
return SKIP;
|
|
}
|
|
if (in_free_list (blk)) {
|
|
free_list_corrupted = 1;
|
|
if (++dup_blocks >= DUP_LIST_SIZE) {
|
|
printf ("EXCESSIVE DUP BLKS IN FREE LIST.\n");
|
|
return STOP;
|
|
}
|
|
} else {
|
|
++*free_blocks;
|
|
mark_free_list (blk);
|
|
}
|
|
return KEEPON;
|
|
}
|
|
|
|
/*
|
|
* Scan a free block list and return a number of free blocks.
|
|
* If the list is corrupted, set 'free_list_corrupted' flag.
|
|
*/
|
|
static unsigned check_free_list (fs_t *fs)
|
|
{
|
|
unsigned *ap, *base;
|
|
unsigned free_blocks, nfree;
|
|
unsigned data [BSDFS_BSIZE / 4];
|
|
unsigned list [NICFREE];
|
|
int i;
|
|
|
|
if (fs->nfree == 0)
|
|
return 0;
|
|
free_blocks = 0;
|
|
nfree = fs->nfree;
|
|
base = fs->free;
|
|
for (;;) {
|
|
if (nfree <= 0 || nfree > NICFREE) {
|
|
printf ("BAD FREEBLK COUNT\n");
|
|
free_list_corrupted = 1;
|
|
break;
|
|
}
|
|
ap = base + nfree;
|
|
while (--ap > base) {
|
|
if (pass5 (fs, *ap, &free_blocks) == STOP)
|
|
return free_blocks;
|
|
}
|
|
if (*ap == 0 || pass5 (fs, *ap, &free_blocks) != KEEPON)
|
|
break;
|
|
if (! fs_read_block (fs, *ap, (unsigned char*) data)) {
|
|
print_io_error ("READ", *ap);
|
|
break;
|
|
}
|
|
nfree = data[0];
|
|
for (i=0; i<NICFREE; ++i)
|
|
list [i] = data[i+1];
|
|
base = list;
|
|
}
|
|
return free_blocks;
|
|
}
|
|
|
|
/*
|
|
* Check a list of free inodes.
|
|
*/
|
|
static void check_free_inode_list (fs_t *fs)
|
|
{
|
|
int i;
|
|
unsigned inum;
|
|
|
|
for (i=0; i<fs->ninode; i++) {
|
|
inum = fs->inode[i];
|
|
if (inode_state (inum) != USTATE) {
|
|
printf ("ALLOCATED INODE(S) IN IFREE LIST\n");
|
|
if (fs->writable) {
|
|
fs->ninode = i - 1;
|
|
while (i < NICINOD)
|
|
fs->inode [i++] = 0;
|
|
fs->dirty = 1;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Build a free block list from scratch.
|
|
*/
|
|
static unsigned make_free_list (fs_t *fs)
|
|
{
|
|
unsigned free_blocks, n;
|
|
|
|
fs->nfree = 0;
|
|
fs->flock = 0;
|
|
fs->fmod = 0;
|
|
fs->ilock = 0;
|
|
fs->ronly = 0;
|
|
fs->dirty = 1;
|
|
free_blocks = 0;
|
|
|
|
/* Build a list of free blocks */
|
|
fs_block_free (fs, 0);
|
|
for (n = fs->fsize - 1; n >= fs->isize; n--) {
|
|
if (block_is_busy (n))
|
|
continue;
|
|
++free_blocks;
|
|
if (! fs_block_free (fs, n))
|
|
return 0;
|
|
}
|
|
return free_blocks;
|
|
}
|
|
|
|
/*
|
|
* Check filesystem for errors.
|
|
* When readonly - just check and print errors.
|
|
* If the system is open on read/write - fix errors.
|
|
*/
|
|
int fs_check (fs_t *fs)
|
|
{
|
|
fs_inode_t inode;
|
|
int n;
|
|
unsigned inum;
|
|
unsigned block_map_size; /* number of free blocks */
|
|
unsigned free_blocks; /* number of free blocks */
|
|
unsigned used_blocks; /* number of blocks used */
|
|
unsigned last_allocated_inode; /* hiwater mark of inodes */
|
|
|
|
if (fs->isize >= fs->fsize) {
|
|
printf ("Bad filesystem size: total %d blocks with %d inode blocks\n",
|
|
fs->fsize, fs->isize);
|
|
return 0;
|
|
}
|
|
free_list_corrupted = 0;
|
|
total_files = 0;
|
|
used_blocks = 0;
|
|
dup_multi = dup_end = &dup_list[0];
|
|
bad_link_end = &bad_link_list[0];
|
|
lost_found_inode = 0;
|
|
buf_dirty = 0;
|
|
buf_bno = (unsigned) -1;
|
|
|
|
/* Allocate memory. */
|
|
block_map_size = (fs->fsize + 7) / 8;
|
|
block_map = calloc (block_map_size, sizeof (*block_map));
|
|
state_map = calloc (((fs->isize-1) * BSDFS_INODES_PER_BLOCK +
|
|
STATES_PER_BYTE) / STATES_PER_BYTE, sizeof (*state_map));
|
|
link_count = calloc ((fs->isize-1) * BSDFS_INODES_PER_BLOCK + 1,
|
|
sizeof (*link_count));
|
|
if (! block_map || ! state_map || ! link_count) {
|
|
printf ("Cannot allocate memory\n");
|
|
fatal: if (block_map)
|
|
free (block_map);
|
|
if (state_map)
|
|
free (state_map);
|
|
if (link_count)
|
|
free (link_count);
|
|
return 0;
|
|
}
|
|
|
|
printf ("** Phase 1 - Check Blocks and Sizes\n");
|
|
last_allocated_inode = 0;
|
|
for (inum = 1; inum <= (fs->isize-1) * BSDFS_INODES_PER_BLOCK; inum++) {
|
|
if (! fs_inode_get (fs, &inode, inum))
|
|
continue;
|
|
if (inode.mode & INODE_MODE_FMT) {
|
|
/*printf ("inode %d: %#o\n", inode.number, inode.mode);*/
|
|
last_allocated_inode = inum;
|
|
total_files++;
|
|
link_count[inum] = inode.nlink;
|
|
if (link_count[inum] <= 0) {
|
|
if (bad_link_end < &bad_link_list[LINK_LIST_SIZE])
|
|
*bad_link_end++ = inum;
|
|
else {
|
|
printf ("LINK COUNT TABLE OVERFLOW\n");
|
|
}
|
|
}
|
|
set_inode_state (inum, ((inode.mode & INODE_MODE_FMT) ==
|
|
INODE_MODE_FDIR) ? DSTATE : FSTATE);
|
|
bad_blocks = dup_blocks = 0;
|
|
scan_inode (&inode, ADDR, pass1, &used_blocks);
|
|
n = inode_state (inum);
|
|
if (n == DSTATE || n == FSTATE) {
|
|
if ((inode.mode & INODE_MODE_FMT) == INODE_MODE_FDIR &&
|
|
(inode.size % 4) != 0) {
|
|
printf ("DIRECTORY MISALIGNED I=%u\n\n",
|
|
inode.number);
|
|
}
|
|
}
|
|
}
|
|
else if (inode.mode != 0) {
|
|
printf ("PARTIALLY ALLOCATED INODE I=%u\n", inum);
|
|
if (fs->writable)
|
|
fs_inode_clear (&inode);
|
|
}
|
|
fs_inode_save (&inode, 0);
|
|
}
|
|
if (dup_end != &dup_list[0]) {
|
|
printf ("** Phase 1b - Rescan For More DUPS\n");
|
|
for (inum = 1; inum <= last_allocated_inode; inum++) {
|
|
if (inode_state (inum) == USTATE)
|
|
continue;
|
|
if (! fs_inode_get (fs, &inode, inum))
|
|
continue;
|
|
if (scan_inode (&inode, ADDR, pass1b, 0) & STOP)
|
|
break;
|
|
}
|
|
}
|
|
|
|
printf ("** Phase 2 - Check Pathnames\n");
|
|
thisname = pathp = pathname;
|
|
switch (inode_state (BSDFS_ROOT_INODE)) {
|
|
case USTATE:
|
|
printf ("ROOT INODE UNALLOCATED. TERMINATING.\n");
|
|
goto fatal;
|
|
case FSTATE:
|
|
printf ("ROOT INODE NOT DIRECTORY\n");
|
|
if (! fs->writable)
|
|
goto fatal;
|
|
if (! fs_inode_get (fs, &inode, BSDFS_ROOT_INODE))
|
|
goto fatal;
|
|
inode.mode &= ~INODE_MODE_FMT;
|
|
inode.mode |= INODE_MODE_FDIR;
|
|
fs_inode_save (&inode, 1);
|
|
set_inode_state (BSDFS_ROOT_INODE, DSTATE);
|
|
case DSTATE:
|
|
scan_pass2 (fs, BSDFS_ROOT_INODE);
|
|
break;
|
|
case CLEAR:
|
|
printf ("DUPS/BAD IN ROOT INODE\n");
|
|
set_inode_state (BSDFS_ROOT_INODE, DSTATE);
|
|
scan_pass2 (fs, BSDFS_ROOT_INODE);
|
|
}
|
|
|
|
printf ("** Phase 3 - Check Connectivity\n");
|
|
for (inum = BSDFS_ROOT_INODE; inum <= last_allocated_inode; inum++) {
|
|
if (inode_state (inum) == DSTATE) {
|
|
unsigned ino;
|
|
|
|
find_inode_name = "..";
|
|
ino = inum;
|
|
do {
|
|
if (! fs_inode_get (fs, &inode, ino))
|
|
break;
|
|
find_inode_result = 0;
|
|
scan_inode (&inode, DATA, scan_directory, find_inode);
|
|
if (find_inode_result == 0) {
|
|
/* Parent link lost. */
|
|
if (move_to_lost_found (&inode)) {
|
|
thisname = pathp = pathname;
|
|
*pathp++ = '?';
|
|
scan_pass2 (fs, ino);
|
|
}
|
|
break;
|
|
}
|
|
ino = find_inode_result;
|
|
} while (inode_state (ino) == DSTATE);
|
|
}
|
|
}
|
|
|
|
printf ("** Phase 4 - Check Reference Counts\n");
|
|
for (inum = BSDFS_ROOT_INODE; inum <= last_allocated_inode; inum++) {
|
|
switch (inode_state (inum)) {
|
|
case FSTATE:
|
|
n = link_count [inum];
|
|
if (n)
|
|
adjust_link_count (fs, inum, n);
|
|
else {
|
|
unsigned *blp;
|
|
|
|
for (blp = bad_link_list; blp < bad_link_end; blp++)
|
|
if (*blp == inum) {
|
|
clear_inode (fs, inum, "UNREF");
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case DSTATE:
|
|
clear_inode (fs, inum, "UNREF");
|
|
break;
|
|
case CLEAR:
|
|
clear_inode (fs, inum, "BAD/DUP");
|
|
}
|
|
}
|
|
buf_flush (fs);
|
|
|
|
printf ("** Phase 5 - Check Free List\n");
|
|
free (link_count);
|
|
check_free_inode_list (fs);
|
|
free (state_map);
|
|
bad_blocks = dup_blocks = 0;
|
|
free_map = calloc (block_map_size, sizeof (*free_map));
|
|
if (! free_map) {
|
|
printf ("NO MEMORY TO CHECK FREE LIST\n");
|
|
free_list_corrupted = 1;
|
|
free_blocks = 0;
|
|
} else {
|
|
memcpy (free_map, block_map, block_map_size);
|
|
free_blocks = check_free_list (fs);
|
|
free (free_map);
|
|
}
|
|
if (bad_blocks)
|
|
printf ("%d BAD BLKS IN FREE LIST\n", bad_blocks);
|
|
if (dup_blocks)
|
|
printf ("%d DUP BLKS IN FREE LIST\n", dup_blocks);
|
|
if (free_list_corrupted == 0) {
|
|
if (used_blocks + free_blocks != fs->fsize - fs->isize) {
|
|
printf ("%d BLK(S) MISSING\n", fs->fsize -
|
|
fs->isize - used_blocks - free_blocks);
|
|
free_list_corrupted = 1;
|
|
}
|
|
}
|
|
if (free_list_corrupted) {
|
|
printf ("BAD FREE LIST\n");
|
|
if (! fs->writable)
|
|
free_list_corrupted = 0;
|
|
}
|
|
|
|
if (free_list_corrupted) {
|
|
printf ("** Phase 6 - Salvage Free List\n");
|
|
free_blocks = make_free_list (fs);
|
|
}
|
|
|
|
printf ("%d files %d blocks %d free\n",
|
|
total_files, used_blocks, free_blocks);
|
|
if (fs->modified) {
|
|
time (&fs->utime);
|
|
fs->dirty = 1;
|
|
}
|
|
buf_flush (fs);
|
|
fs_sync (fs, 0);
|
|
if (fs->modified)
|
|
printf ("\n***** FILE SYSTEM WAS MODIFIED *****\n");
|
|
|
|
free (block_map);
|
|
return 1;
|
|
}
|