NetBSD/sbin/fsck_ffs/inode.c
thorpej 2402aaa614 Bug fix from Kirk McKusick <mckusick@McKusick.COM>:
Fix a bug in fsck_ffs where if a directory somehow develops a hole
(that is a block pointer that has a value of zero), fsck would give the
filesystem a clean bill of health, but the kernel would panic when
accessing the directory with the hole. Fsck now checks for holes
in directories.  If found in preen mode, fsck fails.  In manual
mode, it can be directed to shorten the directory to the beginning of
the hole. A  more complete solution would be to allocate a block to fill
the hole. However, this is a lot more work for a `cannot happen' error,
so the extra effort seems unwarranted.
1996-10-11 20:15:46 +00:00

637 lines
14 KiB
C

/* $NetBSD: inode.c,v 1.23 1996/10/11 20:15:47 thorpej Exp $ */
/*
* Copyright (c) 1980, 1986, 1993
* The Regents of the University of California. All rights reserved.
*
* 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. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. 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 REGENTS 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 REGENTS 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.
*/
#ifndef lint
#if 0
static char sccsid[] = "@(#)inode.c 8.5 (Berkeley) 2/8/95";
#else
static char rcsid[] = "$NetBSD: inode.c,v 1.23 1996/10/11 20:15:47 thorpej Exp $";
#endif
#endif /* not lint */
#include <sys/param.h>
#include <sys/time.h>
#include <ufs/ufs/dinode.h>
#include <ufs/ufs/dir.h>
#include <ufs/ffs/fs.h>
#ifndef SMALL
#include <pwd.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "fsck.h"
#include "fsutil.h"
#include "extern.h"
static ino_t startinum;
static int iblock __P((struct inodesc *, long, u_int64_t));
int
ckinode(dp, idesc)
struct dinode *dp;
register struct inodesc *idesc;
{
register ufs_daddr_t *ap;
long ret, n, ndb, offset;
struct dinode dino;
u_int64_t remsize, sizepb;
mode_t mode;
char pathbuf[MAXPATHLEN + 1];
if (idesc->id_fix != IGNORE)
idesc->id_fix = DONTKNOW;
idesc->id_entryno = 0;
idesc->id_filesize = dp->di_size;
mode = dp->di_mode & IFMT;
if (mode == IFBLK || mode == IFCHR || (mode == IFLNK &&
(dp->di_size < sblock.fs_maxsymlinklen ||
(sblock.fs_maxsymlinklen == 0 && dp->di_blocks == 0))))
return (KEEPON);
dino = *dp;
ndb = howmany(dino.di_size, sblock.fs_bsize);
for (ap = &dino.di_db[0]; ap < &dino.di_db[NDADDR]; ap++) {
if (--ndb == 0 && (offset = blkoff(&sblock, dino.di_size)) != 0)
idesc->id_numfrags =
numfrags(&sblock, fragroundup(&sblock, offset));
else
idesc->id_numfrags = sblock.fs_frag;
if (*ap == 0) {
if (idesc->id_type == DATA && ndb >= 0) {
/* An empty block in a directory XXX */
getpathname(pathbuf, idesc->id_number,
idesc->id_number);
pfatal("DIRECTORY %s: CONTAINS EMPTY BLOCKS",
pathbuf);
if (reply("ADJUST LENGTH") == 1) {
dp = ginode(idesc->id_number);
dp->di_size = (ap - &dino.di_db[0]) *
sblock.fs_bsize;
printf(
"YOU MUST RERUN FSCK AFTERWARDS\n");
rerun = 1;
inodirty();
}
}
continue;
}
idesc->id_blkno = *ap;
if (idesc->id_type == ADDR)
ret = (*idesc->id_func)(idesc);
else
ret = dirscan(idesc);
if (ret & STOP)
return (ret);
}
idesc->id_numfrags = sblock.fs_frag;
remsize = dino.di_size - sblock.fs_bsize * NDADDR;
sizepb = sblock.fs_bsize;
for (ap = &dino.di_ib[0], n = 1; n <= NIADDR; ap++, n++) {
if (*ap) {
idesc->id_blkno = *ap;
ret = iblock(idesc, n, remsize);
if (ret & STOP)
return (ret);
} else {
if (idesc->id_type == DATA && remsize > 0) {
/* An empty block in a directory XXX */
getpathname(pathbuf, idesc->id_number,
idesc->id_number);
pfatal("DIRECTORY %s: CONTAINS EMPTY BLOCKS",
pathbuf);
if (reply("ADJUST LENGTH") == 1) {
dp = ginode(idesc->id_number);
dp->di_size -= remsize;
remsize = 0;
printf(
"YOU MUST RERUN FSCK AFTERWARDS\n");
rerun = 1;
inodirty();
break;
}
}
}
sizepb *= NINDIR(&sblock);
remsize -= sizepb;
}
return (KEEPON);
}
static int
iblock(idesc, ilevel, isize)
struct inodesc *idesc;
long ilevel;
u_int64_t isize;
{
register daddr_t *ap;
register daddr_t *aplim;
register struct bufarea *bp;
int i, n, (*func) __P((struct inodesc *)), nif;
u_int64_t sizepb;
char buf[BUFSIZ];
char pathbuf[MAXPATHLEN + 1];
struct dinode *dp;
if (idesc->id_type == ADDR) {
func = idesc->id_func;
if (((n = (*func)(idesc)) & KEEPON) == 0)
return (n);
} else
func = dirscan;
if (chkrange(idesc->id_blkno, idesc->id_numfrags))
return (SKIP);
bp = getdatablk(idesc->id_blkno, sblock.fs_bsize);
ilevel--;
for (sizepb = sblock.fs_bsize, i = 0; i < ilevel; i++)
sizepb *= NINDIR(&sblock);
if (isize > sizepb * NINDIR(&sblock))
nif = NINDIR(&sblock);
else
nif = howmany(isize, sizepb);
if (idesc->id_func == pass1check && nif < NINDIR(&sblock)) {
aplim = &bp->b_un.b_indir[NINDIR(&sblock)];
for (ap = &bp->b_un.b_indir[nif]; ap < aplim; ap++) {
if (*ap == 0)
continue;
(void)sprintf(buf, "PARTIALLY TRUNCATED INODE I=%u",
idesc->id_number);
if (dofix(idesc, buf)) {
*ap = 0;
dirty(bp);
}
}
flush(fswritefd, bp);
}
aplim = &bp->b_un.b_indir[nif];
for (ap = bp->b_un.b_indir; ap < aplim; ap++) {
if (*ap) {
idesc->id_blkno = *ap;
if (ilevel == 0)
n = (*func)(idesc);
else
n = iblock(idesc, ilevel, isize);
if (n & STOP) {
bp->b_flags &= ~B_INUSE;
return (n);
}
} else {
if (idesc->id_type == DATA && isize > 0) {
/* An empty block in a directory XXX */
getpathname(pathbuf, idesc->id_number,
idesc->id_number);
pfatal("DIRECTORY %s: CONTAINS EMPTY BLOCKS",
pathbuf);
if (reply("ADJUST LENGTH") == 1) {
dp = ginode(idesc->id_number);
dp->di_size -= isize;
isize = 0;
printf(
"YOU MUST RERUN FSCK AFTERWARDS\n");
rerun = 1;
inodirty();
bp->b_flags &= ~B_INUSE;
return(STOP);
}
}
}
isize -= sizepb;
}
bp->b_flags &= ~B_INUSE;
return (KEEPON);
}
/*
* Check that a block in a legal block number.
* Return 0 if in range, 1 if out of range.
*/
int
chkrange(blk, cnt)
daddr_t blk;
int cnt;
{
register int c;
if ((unsigned)(blk + cnt) > maxfsblock)
return (1);
c = dtog(&sblock, blk);
if (blk < cgdmin(&sblock, c)) {
if ((blk + cnt) > cgsblock(&sblock, c)) {
if (debug) {
printf("blk %d < cgdmin %d;",
blk, cgdmin(&sblock, c));
printf(" blk + cnt %d > cgsbase %d\n",
blk + cnt, cgsblock(&sblock, c));
}
return (1);
}
} else {
if ((blk + cnt) > cgbase(&sblock, c+1)) {
if (debug) {
printf("blk %d >= cgdmin %d;",
blk, cgdmin(&sblock, c));
printf(" blk + cnt %d > sblock.fs_fpg %d\n",
blk+cnt, sblock.fs_fpg);
}
return (1);
}
}
return (0);
}
/*
* General purpose interface for reading inodes.
*/
struct dinode *
ginode(inumber)
ino_t inumber;
{
daddr_t iblk;
if (inumber < ROOTINO || inumber > maxino)
errexit("bad inode number %d to ginode\n", inumber);
if (startinum == 0 ||
inumber < startinum || inumber >= startinum + INOPB(&sblock)) {
iblk = ino_to_fsba(&sblock, inumber);
if (pbp != 0)
pbp->b_flags &= ~B_INUSE;
pbp = getdatablk(iblk, sblock.fs_bsize);
startinum = (inumber / INOPB(&sblock)) * INOPB(&sblock);
}
return (&pbp->b_un.b_dinode[inumber % INOPB(&sblock)]);
}
/*
* Special purpose version of ginode used to optimize first pass
* over all the inodes in numerical order.
*/
ino_t nextino, lastinum;
long readcnt, readpercg, fullcnt, inobufsize, partialcnt, partialsize;
struct dinode *inodebuf;
struct dinode *
getnextinode(inumber)
ino_t inumber;
{
long size;
daddr_t dblk;
static struct dinode *dp;
if (inumber != nextino++ || inumber > maxino)
errexit("bad inode number %d to nextinode\n", inumber);
if (inumber >= lastinum) {
readcnt++;
dblk = fsbtodb(&sblock, ino_to_fsba(&sblock, lastinum));
if (readcnt % readpercg == 0) {
size = partialsize;
lastinum += partialcnt;
} else {
size = inobufsize;
lastinum += fullcnt;
}
(void)bread(fsreadfd, (char *)inodebuf, dblk, size); /* ??? */
dp = inodebuf;
}
return (dp++);
}
void
resetinodebuf()
{
startinum = 0;
nextino = 0;
lastinum = 0;
readcnt = 0;
inobufsize = blkroundup(&sblock, INOBUFSIZE);
fullcnt = inobufsize / sizeof(struct dinode);
readpercg = sblock.fs_ipg / fullcnt;
partialcnt = sblock.fs_ipg % fullcnt;
partialsize = partialcnt * sizeof(struct dinode);
if (partialcnt != 0) {
readpercg++;
} else {
partialcnt = fullcnt;
partialsize = inobufsize;
}
if (inodebuf == NULL &&
(inodebuf = (struct dinode *)malloc((unsigned)inobufsize)) == NULL)
errexit("Cannot allocate space for inode buffer\n");
while (nextino < ROOTINO)
(void)getnextinode(nextino);
}
void
freeinodebuf()
{
if (inodebuf != NULL)
free((char *)inodebuf);
inodebuf = NULL;
}
/*
* Routines to maintain information about directory inodes.
* This is built during the first pass and used during the
* second and third passes.
*
* Enter inodes into the cache.
*/
void
cacheino(dp, inumber)
register struct dinode *dp;
ino_t inumber;
{
register struct inoinfo *inp;
struct inoinfo **inpp;
unsigned int blks;
blks = howmany(dp->di_size, sblock.fs_bsize);
if (blks > NDADDR)
blks = NDADDR + NIADDR;
inp = (struct inoinfo *)
malloc(sizeof(*inp) + (blks - 1) * sizeof(daddr_t));
if (inp == NULL)
return;
inpp = &inphead[inumber % numdirs];
inp->i_nexthash = *inpp;
*inpp = inp;
inp->i_child = inp->i_sibling = inp->i_parentp = 0;
if (inumber == ROOTINO)
inp->i_parent = ROOTINO;
else
inp->i_parent = (ino_t)0;
inp->i_dotdot = (ino_t)0;
inp->i_number = inumber;
inp->i_isize = dp->di_size;
inp->i_numblks = blks * sizeof(daddr_t);
memcpy(&inp->i_blks[0], &dp->di_db[0], (size_t)inp->i_numblks);
if (inplast == listmax) {
listmax += 100;
inpsort = (struct inoinfo **)realloc((char *)inpsort,
(unsigned)listmax * sizeof(struct inoinfo *));
if (inpsort == NULL)
errexit("cannot increase directory list");
}
inpsort[inplast++] = inp;
}
/*
* Look up an inode cache structure.
*/
struct inoinfo *
getinoinfo(inumber)
ino_t inumber;
{
register struct inoinfo *inp;
for (inp = inphead[inumber % numdirs]; inp; inp = inp->i_nexthash) {
if (inp->i_number != inumber)
continue;
return (inp);
}
errexit("cannot find inode %d\n", inumber);
return ((struct inoinfo *)0);
}
/*
* Clean up all the inode cache structure.
*/
void
inocleanup()
{
register struct inoinfo **inpp;
if (inphead == NULL)
return;
for (inpp = &inpsort[inplast - 1]; inpp >= inpsort; inpp--)
free((char *)(*inpp));
free((char *)inphead);
free((char *)inpsort);
inphead = inpsort = NULL;
}
void
inodirty()
{
dirty(pbp);
}
void
clri(idesc, type, flag)
register struct inodesc *idesc;
char *type;
int flag;
{
register struct dinode *dp;
dp = ginode(idesc->id_number);
if (flag == 1) {
pwarn("%s %s", type,
(dp->di_mode & IFMT) == IFDIR ? "DIR" : "FILE");
pinode(idesc->id_number);
}
if (preen || reply("CLEAR") == 1) {
if (preen)
printf(" (CLEARED)\n");
n_files--;
(void)ckinode(dp, idesc);
clearinode(dp);
statemap[idesc->id_number] = USTATE;
inodirty();
}
}
int
findname(idesc)
struct inodesc *idesc;
{
register struct direct *dirp = idesc->id_dirp;
if (dirp->d_ino != idesc->id_parent)
return (KEEPON);
memcpy(idesc->id_name, dirp->d_name, (size_t)dirp->d_namlen + 1);
return (STOP|FOUND);
}
int
findino(idesc)
struct inodesc *idesc;
{
register struct direct *dirp = idesc->id_dirp;
if (dirp->d_ino == 0)
return (KEEPON);
if (strcmp(dirp->d_name, idesc->id_name) == 0 &&
dirp->d_ino >= ROOTINO && dirp->d_ino <= maxino) {
idesc->id_parent = dirp->d_ino;
return (STOP|FOUND);
}
return (KEEPON);
}
void
pinode(ino)
ino_t ino;
{
register struct dinode *dp;
register char *p;
struct passwd *pw;
time_t t;
printf(" I=%u ", ino);
if (ino < ROOTINO || ino > maxino)
return;
dp = ginode(ino);
printf(" OWNER=");
#ifndef SMALL
if ((pw = getpwuid((int)dp->di_uid)) != 0)
printf("%s ", pw->pw_name);
else
#endif
printf("%u ", (unsigned)dp->di_uid);
printf("MODE=%o\n", dp->di_mode);
if (preen)
printf("%s: ", cdevname());
printf("SIZE=%qu ", dp->di_size);
t = dp->di_mtime;
p = ctime(&t);
printf("MTIME=%12.12s %4.4s ", &p[4], &p[20]);
}
void
blkerror(ino, type, blk)
ino_t ino;
char *type;
daddr_t blk;
{
pfatal("%d %s I=%u", blk, type, ino);
printf("\n");
switch (statemap[ino]) {
case FSTATE:
statemap[ino] = FCLEAR;
return;
case DSTATE:
statemap[ino] = DCLEAR;
return;
case FCLEAR:
case DCLEAR:
return;
default:
errexit("BAD STATE %d TO BLKERR", statemap[ino]);
/* NOTREACHED */
}
}
/*
* allocate an unused inode
*/
ino_t
allocino(request, type)
ino_t request;
int type;
{
register ino_t ino;
register struct dinode *dp;
time_t t;
if (request == 0)
request = ROOTINO;
else if (statemap[request] != USTATE)
return (0);
for (ino = request; ino < maxino; ino++)
if (statemap[ino] == USTATE)
break;
if (ino == maxino)
return (0);
switch (type & IFMT) {
case IFDIR:
statemap[ino] = DSTATE;
break;
case IFREG:
case IFLNK:
statemap[ino] = FSTATE;
break;
default:
return (0);
}
dp = ginode(ino);
dp->di_db[0] = allocblk((long)1);
if (dp->di_db[0] == 0) {
statemap[ino] = USTATE;
return (0);
}
dp->di_mode = type;
(void)time(&t);
dp->di_atime = t;
dp->di_mtime = dp->di_ctime = dp->di_atime;
dp->di_size = sblock.fs_fsize;
dp->di_blocks = btodb(sblock.fs_fsize);
n_files++;
inodirty();
if (newinofmt)
typemap[ino] = IFTODT(type);
return (ino);
}
/*
* deallocate an inode
*/
void
freeino(ino)
ino_t ino;
{
struct inodesc idesc;
struct dinode *dp;
memset(&idesc, 0, sizeof(struct inodesc));
idesc.id_type = ADDR;
idesc.id_func = pass4check;
idesc.id_number = ino;
dp = ginode(ino);
(void)ckinode(dp, &idesc);
clearinode(dp);
inodirty();
statemap[ino] = USTATE;
n_files--;
}