/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*
 * Copyright (c) 1980, 1986, 1990 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that: (1) source distributions retain this entire copyright
 * notice and comment, and (2) distributions including binaries display
 * the following acknowledgement:  ``This product includes software
 * developed by the University of California, Berkeley and its contributors''
 * in the documentation or other materials provided with the distribution
 * and in all advertising materials mentioning features or use of this
 * software. 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 ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <stdio.h>
#include <stdlib.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/mntent.h>
#include <sys/fs/ufs_fs.h>
#include <sys/vnode.h>
#include <sys/fs/ufs_inode.h>
#define	_KERNEL
#include <sys/fs/ufs_fsdir.h>
#undef _KERNEL
#include <string.h>
#include "fsck.h"

#define	MINDIRSIZE	(sizeof (struct dirtemplate))

static int blksort(const void *, const void *);
static int pass2check(struct inodesc *);

void
pass2(void)
{
	struct dinode 		*dp, *dp2, *dpattr;
	struct inoinfo 		**inpp, *inp;
	struct inoinfo 		**inpend;
	struct inodesc 		curino;
	struct inodesc 		ldesc;
	struct dinode 		dino;
	char 			pathbuf[MAXPATHLEN + 1];
	int			found;
	int			dirtype;
	caddr_t			errmsg;
	struct shadowclientinfo *sci;

	switch (statemap[UFSROOTINO] & ~INDELAYD) {
	case USTATE:
		pfatal("ROOT INODE UNALLOCATED");
		if (reply("ALLOCATE") == 0) {
			errexit("Program terminated.");
		}
		if (allocdir(UFSROOTINO, UFSROOTINO, 0755, 0) != UFSROOTINO)
			errexit("CANNOT ALLOCATE ROOT INODE\n");
		break;

	case DCLEAR:
		pfatal("DUPS/BAD IN ROOT INODE");
		if (reply("REALLOCATE") == 1) {
			freeino(UFSROOTINO, TI_NOPARENT);
			if (allocdir(UFSROOTINO, UFSROOTINO,
			    0755, 0) != UFSROOTINO)
				errexit("CANNOT ALLOCATE ROOT INODE\n");
			break;
		}
		if (reply("CONTINUE") == 0) {
			errexit("Program terminated.");
		}
		break;

	case FSTATE:
	case FCLEAR:
	case FZLINK:
	case SSTATE:
	case SCLEAR:
		pfatal("ROOT INODE NOT DIRECTORY");
		if (reply("REALLOCATE") == 1) {
			freeino(UFSROOTINO, TI_NOPARENT);
			if (allocdir(UFSROOTINO, UFSROOTINO, 0755, 0) !=
			    UFSROOTINO)
				errexit("CANNOT ALLOCATE ROOT INODE\n");
			break;
		}
		if (reply("FIX") == 0) {
			ckfini();
			errexit("Program terminated.");
		}
		dp = ginode(UFSROOTINO);
		dp->di_mode &= ~IFMT;
		dp->di_mode |= IFDIR;
		inodirty();
		break;

	case DSTATE:
	case DZLINK:
		break;

	default:
		errexit("BAD STATE 0x%x FOR ROOT INODE\n",
			statemap[UFSROOTINO]);
	}
	statemap[UFSROOTINO] = DFOUND;

	/*
	 * Technically, we do know who the parent is.  However,
	 * if this is set, then we'll get confused during the
	 * second-dir-entry-is-dotdot test for the root inode.
	 */
	inp = getinoinfo(UFSROOTINO);
	if (inp != NULL && inp->i_dotdot != 0)
		inp->i_dotdot = 0;

	/*
	 * Sort the directory list into disk block order.  There's no
	 * requirement to do this, but it may help improve our i/o times
	 * somewhat.
	 */
	qsort((void *)inpsort, (size_t)inplast, sizeof (*inpsort), blksort);
	/*
	 * Check the integrity of each directory.  In general, we treat
	 * attribute directories just like normal ones.  Only the handling
	 * of .. is really different.
	 */
	(void) memset(&dino, 0, sizeof (struct dinode));
	dino.di_mode = IFDIR;
	inpend = &inpsort[inplast];
	for (inpp = inpsort; inpp < inpend; inpp++) {
		inp = *inpp;

		if (inp->i_isize == 0)
			continue;

		/* != DSTATE also covers case of == USTATE */
		if (((statemap[inp->i_number] & STMASK) != DSTATE) ||
		    ((statemap[inp->i_number] & INCLEAR) == INCLEAR))
			continue;

		if (inp->i_isize < (offset_t)MINDIRSIZE) {
			direrror(inp->i_number, "DIRECTORY TOO SHORT");
			inp->i_isize = (offset_t)roundup(MINDIRSIZE, DIRBLKSIZ);
			if (reply("FIX") == 1) {
				dp = ginode(inp->i_number);
				dp->di_size = (u_offset_t)inp->i_isize;
				inodirty();
			} else {
				iscorrupt = 1;
			}
		}
		if ((inp->i_isize & (offset_t)(DIRBLKSIZ - 1)) != 0) {
			getpathname(pathbuf, inp->i_number, inp->i_number);
			pwarn("DIRECTORY %s: LENGTH %lld NOT MULTIPLE OF %d",
			    pathbuf, (longlong_t)inp->i_isize, DIRBLKSIZ);
			inp->i_isize = roundup(inp->i_isize,
					(offset_t)DIRBLKSIZ);
			if (preen || reply("ADJUST") == 1) {
				dp = ginode(inp->i_number);
				dp->di_size =
					(u_offset_t)roundup(inp->i_isize,
						    (offset_t)DIRBLKSIZ);
				inodirty();
				if (preen)
					(void) printf(" (ADJUSTED)\n");
			} else {
				iscorrupt = 1;
			}
		}
		dp = ginode(inp->i_number);
		if ((dp->di_mode & IFMT) == IFATTRDIR &&
		    (dp->di_cflags & IXATTR) == 0) {
			pwarn("ATTRIBUTE DIRECTORY  I=%d  MISSING IXATTR FLAG",
			    inp->i_number);
			if (preen || reply("CORRECT") == 1) {
				dp->di_cflags |= IXATTR;
				inodirty();
				if (preen)
					(void) printf(" (CORRECTED)\n");
			}
		}
		dp = &dino;
		dp->di_size = (u_offset_t)inp->i_isize;
		(void) memmove((void *)&dp->di_db[0], (void *)&inp->i_blks[0],
			inp->i_blkssize);
		init_inodesc(&curino);
		curino.id_type = DATA;
		curino.id_func = pass2check;
		curino.id_number = inp->i_number;
		curino.id_parent = inp->i_parent;
		curino.id_fix = DONTKNOW;
		(void) ckinode(dp, &curino, CKI_TRAVERSE);

		/*
		 * Make sure we mark attrdirs as DFOUND, since they won't
		 * be located during normal scan of standard directories.
		 */
		if (curino.id_parent == 0) {
			dpattr = ginode(inp->i_number);
			if ((dpattr->di_mode & IFMT) == IFATTRDIR) {
				for (sci = attrclientinfo; sci != NULL;
				    sci = sci->next) {
					if (sci->shadow == inp->i_number) {
						curino.id_parent =
						    sci->clients->client[0];
						statemap[inp->i_number] =
						    DFOUND;
						inp->i_parent =
						    curino.id_parent;
					}
				}
			}
		}
	}
	/*
	 * Now that the parents of all directories have been found,
	 * make another pass to verify the value of ..
	 */
	for (inpp = inpsort; inpp < inpend; inpp++) {
		inp = *inpp;
		if (inp->i_parent == 0 || inp->i_isize == 0)
			continue;
		/*
		 * There are only directories in inpsort[], so only
		 * directory-related states need to be checked.  There
		 * should never be any flags associated with USTATE.
		 */
		if ((statemap[inp->i_number] & STMASK) == DCLEAR ||
		    statemap[inp->i_number] == USTATE) {
			continue;
		}
		if (statemap[inp->i_parent] == DFOUND &&
		    S_IS_DUNFOUND(statemap[inp->i_number])) {
			statemap[inp->i_number] = DFOUND |
				(statemap[inp->i_number] & INCLEAR);
		}
		if (inp->i_dotdot == inp->i_parent ||
		    inp->i_dotdot == (fsck_ino_t)-1) {
			continue;
		}
		if (inp->i_dotdot == 0) {
			inp->i_dotdot = inp->i_parent;
			fileerror(inp->i_parent, inp->i_number,
			    "MISSING '..'");
			if (reply("FIX") == 0) {
				iscorrupt = 1;
				continue;
			}
			dp = ginode(inp->i_number);
			found = 0;
			dirtype = (dp->di_mode & IFMT);

			/*
			 * See if this is an attrdir that we located in pass1.
			 * i.e. it was on an i_oeftflag of some other inode.
			 * if it isn't found then we have an orphaned attrdir
			 * that needs to be tossed into lost+found.
			 */
			if (dirtype == IFATTRDIR) {
				for (sci = attrclientinfo;
				    sci != NULL;
				    sci = sci->next) {
					if (sci->shadow == inp->i_number) {
						inp->i_parent =
						    sci->clients->client[0];
						found = 1;
					}
				}
			}

			/*
			 * We've already proven there's no "..", so this
			 * can't create a duplicate.
			 */
			if (makeentry(inp->i_number, inp->i_parent, "..")) {

				/*
				 * is it an orphaned attrdir?
				 */
				if (dirtype == IFATTRDIR && found == 0) {
					/*
					 * Throw it into lost+found
					 */
					if (linkup(inp->i_number, lfdir,
					    NULL) == 0) {
						pwarn(
			    "Unable to move attrdir I=%d to lost+found\n",
						    inp->i_number);
						iscorrupt = 1;
					}
					maybe_convert_attrdir_to_dir(
					    inp->i_number);
				}
				if (dirtype == IFDIR) {
					LINK_RANGE(errmsg,
					    lncntp[inp->i_parent], -1);
					if (errmsg != NULL) {
						LINK_CLEAR(errmsg,
						    inp->i_parent, IFDIR,
						    &ldesc);
						if (statemap[inp->i_parent] !=
						    USTATE) {
							/*
							 * iscorrupt is
							 * already set
							 */
							continue;
						}
					}
					TRACK_LNCNTP(inp->i_parent,
					    lncntp[inp->i_parent]--);
				}

				continue;
			}
			pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '..'\n");
			iscorrupt = 1;
			inp->i_dotdot = (fsck_ino_t)-1;
			continue;
		}

		dp2 = ginode(inp->i_parent);

		if ((dp2->di_mode & IFMT) == IFATTRDIR) {
			continue;
		}
		fileerror(inp->i_parent, inp->i_number,
			"BAD INODE NUMBER FOR '..'");
		if (reply("FIX") == 0) {
			iscorrupt = 1;
			continue;
		}

		LINK_RANGE(errmsg, lncntp[inp->i_dotdot], 1);
		if (errmsg != NULL) {
			LINK_CLEAR(errmsg, inp->i_dotdot, IFDIR, &ldesc);
			if (statemap[inp->i_dotdot] != USTATE) {
				/* iscorrupt is already set */
				continue;
			}
		}
		TRACK_LNCNTP(inp->i_dotdot, lncntp[inp->i_dotdot]++);

		LINK_RANGE(errmsg, lncntp[inp->i_parent], -1);
		if (errmsg != NULL) {
			LINK_CLEAR(errmsg, inp->i_parent, IFDIR, &ldesc);
			if (statemap[inp->i_parent] != USTATE) {
				/* iscorrupt is already set */
				continue;
			}
		}
		TRACK_LNCNTP(inp->i_parent, lncntp[inp->i_parent]--);

		inp->i_dotdot = inp->i_parent;
		(void) changeino(inp->i_number, "..", inp->i_parent);
	}
	/*
	 * Mark all the directories that can be found from the root.
	 */
	propagate();
}

/*
 * Sanity-check a single directory entry.  Which entry is being
 * examined is tracked via idesc->id_entryno.  There are two
 * special ones, 0 (.) and 1 (..).  Those have to exist in order
 * in the first two locations in the directory, and have the usual
 * properties.  All other entries have to not be for either of
 * the special two, and the inode they reference has to be
 * reasonable.
 *
 * This is only called from dirscan(), which looks for the
 * ALTERED flag after each invocation.  If it finds it, the
 * relevant buffer gets pushed out, so we don't have to worry
 * about it here.
 */
#define	PASS2B_PROMPT	"REMOVE DIRECTORY ENTRY FROM I=%d"

static int
pass2check(struct inodesc *idesc)
{
	struct direct *dirp = idesc->id_dirp;
	struct inodesc ldesc;
	struct inoinfo *inp;
	short reclen, entrysize;
	int ret = 0;
	int act, update_lncntp;
	struct dinode *dp, *pdirp, *attrdirp;
	caddr_t errmsg;
	struct direct proto;
	char namebuf[MAXPATHLEN + 1];
	char pathbuf[MAXPATHLEN + 1];
	int isattr;
	int pdirtype;
	int breakout = 0;
	int dontreconnect;

	if (idesc->id_entryno != 0)
		goto chk1;
	/*
	 * check for "."
	 */
	if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") == 0) {
		if (dirp->d_ino != idesc->id_number) {
			direrror(idesc->id_number, "BAD INODE NUMBER FOR '.'");
			dirp->d_ino = idesc->id_number;
			if (reply("FIX") == 1) {
				ret |= ALTERED;
			} else {
				iscorrupt = 1;
			}
		}
		goto chk1;
	}
	/*
	 * Build up a new one, and make sure there's room to put
	 * it where it belongs.
	 */
	direrror(idesc->id_number, "MISSING '.'");
	proto.d_ino = idesc->id_number;
	proto.d_namlen = 1;
	(void) strcpy(proto.d_name, ".");
	entrysize = DIRSIZ(&proto);
	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") != 0) {
		pfatal("CANNOT FIX, FIRST ENTRY IN DIRECTORY CONTAINS %s\n",
			dirp->d_name);
		iscorrupt = 1;
	} else if ((int)dirp->d_reclen < entrysize) {
		pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '.'\n");
		iscorrupt = 1;
	} else if ((int)dirp->d_reclen < 2 * entrysize) {
		/*
		 * No room for another entry after us ("." is the
		 * smallest entry you can have), so just put all
		 * of the old entry's space into the new entry.
		 *
		 * Because we don't touch id_entryno, we end up going
		 * through the chk2 tests as well.
		 */
		proto.d_reclen = dirp->d_reclen;
		(void) memmove((void *)dirp, (void *)&proto,
		    (size_t)entrysize);
		if (reply("FIX") == 1) {
			ret |= ALTERED;
		} else {
			iscorrupt = 1;
		}
	} else {
		/*
		 * There's enough room for an entire additional entry
		 * after this, so create the "." entry and follow it
		 * with an empty entry that covers the rest of the
		 * space.
		 *
		 * The increment of id_entryno means we'll skip the
		 * "." case of chk1, doing the ".." tests instead.
		 * Since we know that there's not a ".." where it
		 * should be (because we just created an empty entry
		 * there), that's the best way of getting it recreated
		 * as well.
		 */
		reclen = dirp->d_reclen - entrysize;
		proto.d_reclen = entrysize;
		(void) memmove((void *)dirp, (void *)&proto,
		    (size_t)entrysize);
		idesc->id_entryno++;
		/*
		 * Make sure the link count is in range before updating
		 * it.  This makes the assumption that the link count
		 * for this inode included one for ".", even though
		 * there wasn't a "." entry.  Even if that's not true,
		 * it's a reasonable working hypothesis, and the link
		 * count verification done in pass4 will fix it for
		 * us anyway.
		 */
		LINK_RANGE(errmsg, lncntp[dirp->d_ino], -1);
		if (errmsg != NULL) {
			LINK_CLEAR(errmsg, dirp->d_ino, IFDIR, &ldesc);
			if (statemap[dirp->d_ino] == USTATE) {
				/*
				 * The inode got zapped, so reset the
				 * directory entry.  Extend it to also
				 * cover the space we were going to make
				 * into a new entry.
				 */
				dirp->d_ino = 0;
				dirp->d_reclen += reclen;
				ret |= ALTERED;
				return (ret);
			}
		}

		/*
		 * Create the new empty entry.
		 */
		/* LINTED pointer cast alignment (entrysize is valid) */
		dirp = (struct direct *)((char *)(dirp) + entrysize);
		(void) memset((void *)dirp, 0, (size_t)reclen);
		dirp->d_reclen = reclen;

		/*
		 * Did the user want us to create a new "."?  This
		 * query assumes that the direrror(MISSING) was the
		 * last thing printed, so if the LINK_RANGE() check
		 * fails, it can't pass through here.
		 */
		if (reply("FIX") == 1) {
			TRACK_LNCNTP(idesc->id_number,
			    lncntp[idesc->id_number]--);
			ret |= ALTERED;
		} else {
			iscorrupt = 1;
		}
	}

	/*
	 * XXX The next few lines are needed whether we're processing "."
	 * or "..".  However, there are some extra steps still needed
	 * for the former, hence the big block of code for
	 * id_entryno == 0.  Alternatively, there could be a label just
	 * before this comment, and everything through the end of that
	 * block moved there.  In some ways, that might make the
	 * control flow more logical (factoring out to separate functions
	 * would be even better).
	 */

chk1:
	if (idesc->id_entryno > 1)
		goto chk2;
	inp = getinoinfo(idesc->id_number);
	if (inp == NULL) {
		/*
		 * This is a can't-happen, since inodes get cached before
		 * we get called on them.
		 */
		errexit("pass2check got NULL from getinoinfo at chk1 I=%d\n",
			idesc->id_number);
	}
	proto.d_ino = inp->i_parent;
	proto.d_namlen = 2;
	(void) strcpy(proto.d_name, "..");
	entrysize = DIRSIZ(&proto);
	if (idesc->id_entryno == 0) {
		/*
		 * We may not actually need to split things up, but if
		 * there's room to do so, we should, as that implies
		 * that the "." entry is larger than it is supposed
		 * to be, and therefore there's something wrong, albeit
		 * possibly harmlessly so.
		 */
		reclen = DIRSIZ(dirp);
		if ((int)dirp->d_reclen < reclen + entrysize) {
			/*
			 * Not enough room for inserting a ".." after
			 * the "." entry.
			 */
			goto chk2;
		}
		/*
		 * There's enough room for an entire additional entry
		 * after "."'s, so split it up.  There's no reason "."
		 * should be bigger than the minimum, so shrink it to
		 * fit, too.  Since by the time we're done with this
		 * part, dirp will be pointing at where ".." should be,
		 * update id_entryno to show that that's the entry
		 * we're on.
		 */
		proto.d_reclen = dirp->d_reclen - reclen;
		dirp->d_reclen = reclen;
		idesc->id_entryno++;
		if (dirp->d_ino > 0 && dirp->d_ino <= maxino) {
			/*
			 * Account for the link to ourselves.
			 */
			LINK_RANGE(errmsg, lncntp[dirp->d_ino], -1);
			if (errmsg != NULL) {
				LINK_CLEAR(errmsg, dirp->d_ino, IFDIR, &ldesc);
				if (statemap[dirp->d_ino] == USTATE) {
					/*
					 * We were going to split the entry
					 * up, but the link count overflowed.
					 * Since we got rid of the inode,
					 * we need to also zap the directory
					 * entry, and restoring the original
					 * state of things is the least-bad
					 * result.
					 */
					dirp->d_ino = 0;
					dirp->d_reclen += proto.d_reclen;
					ret |= ALTERED;
					return (ret);
				}
			}
			TRACK_LNCNTP(dirp->d_ino, lncntp[dirp->d_ino]--);
			/*
			 * Make sure the new entry doesn't get interpreted
			 * as having actual content.
			 */
			/* LINTED pointer cast alignment (reclen is valid) */
			dirp = (struct direct *)((char *)(dirp) + reclen);
			(void) memset((void *)dirp, 0, (size_t)proto.d_reclen);
			dirp->d_reclen = proto.d_reclen;
		} else {
			/*
			 * Everything was fine, up until we realized that
			 * the indicated inode was impossible.  By clearing
			 * d_ino here, we'll trigger the recreation of it
			 * down below, using i_parent.  Unlike the other
			 * half of this if(), we're everything so it shows
			 * that we're still on the "." entry.
			 */
			fileerror(idesc->id_number, dirp->d_ino,
						"I OUT OF RANGE");
			dirp->d_ino = 0;
			if (reply("FIX") == 1) {
				ret |= ALTERED;
			} else {
				iscorrupt = 1;
			}
		}
	}
	/*
	 * Record this ".." inode, but only if we haven't seen one before.
	 * If this isn't the first, it'll get cleared below, and so we
	 * want to remember the entry that'll still be around later.
	 */
	if (dirp->d_ino != 0 && inp->i_dotdot == 0 &&
	    strcmp(dirp->d_name, "..") == 0) {
		inp->i_dotdot = dirp->d_ino;
		goto chk2;
	}
	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") != 0) {
		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
		pfatal("CANNOT FIX, SECOND ENTRY IN DIRECTORY CONTAINS %s\n",
			dirp->d_name);
		iscorrupt = 1;
		inp->i_dotdot = (fsck_ino_t)-1;
	} else if ((int)dirp->d_reclen < entrysize) {
		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
		pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '..'\n");
		/* XXX Same consideration as immediately above. */
		iscorrupt = 1;
		inp->i_dotdot = (fsck_ino_t)-1;
	} else if (inp->i_parent != 0) {
		/*
		 * We know the parent, so fix now.
		 */
		proto.d_ino = inp->i_dotdot = inp->i_parent;
		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
		/*
		 * Lint won't be quiet about d_reclen being set but not
		 * used.  It apparently doesn't understand the implications
		 * of calling memmove(), and won't believe us that it's ok.
		 */
		proto.d_reclen = dirp->d_reclen;
		(void) memmove((void *)dirp, (void *)&proto,
		    (size_t)entrysize);
		if (reply("FIX") == 1) {
			ret |= ALTERED;
		} else {
			iscorrupt = 1;
		}
	} else if (inp->i_number == UFSROOTINO) {
		/*
		 * Always know parent of root inode, so fix now.
		 */
		proto.d_ino = inp->i_dotdot = inp->i_parent = UFSROOTINO;
		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
		/*
		 * Lint won't be quiet about d_reclen being set but not
		 * used.  It apparently doesn't understand the implications
		 * of calling memmove(), and won't believe us that it's ok.
		 */
		proto.d_reclen = dirp->d_reclen;
		(void) memmove((void *)dirp, (void *)&proto, (size_t)entrysize);
		if (reply("FIX") == 1) {
			ret |= ALTERED;
		} else {
			iscorrupt = 1;
		}
	}
	idesc->id_entryno++;
	if (dirp->d_ino != 0) {
		LINK_RANGE(errmsg, lncntp[dirp->d_ino], -1);
		if (errmsg != NULL) {
			LINK_CLEAR(errmsg, dirp->d_ino, IFDIR, &ldesc);
			if (statemap[dirp->d_ino] == USTATE) {
				dirp->d_ino = 0;
				ret |= ALTERED;
			}
		}
		TRACK_LNCNTP(dirp->d_ino, lncntp[dirp->d_ino]--);
	}
	return (ret|KEEPON);
chk2:
	if (dirp->d_ino == 0)
		return (ret|KEEPON);
	if (dirp->d_namlen <= 2 &&
	    dirp->d_name[0] == '.' &&
	    idesc->id_entryno >= 2) {
		if (dirp->d_namlen == 1) {
			direrror(idesc->id_number, "EXTRA '.' ENTRY");
			dirp->d_ino = 0;
			if (reply("FIX") == 1) {
				ret |= ALTERED;
			} else {
				iscorrupt = 1;
			}
			return (KEEPON | ret);
		}
		if (dirp->d_name[1] == '.') {
			direrror(idesc->id_number, "EXTRA '..' ENTRY");
			dirp->d_ino = 0;
			if (reply("FIX") == 1) {
				ret |= ALTERED;
			} else {
				iscorrupt = 1;
			}
			return (KEEPON | ret);
		}
	}
	/*
	 * Because of this increment, all tests for skipping . and ..
	 * below are ``> 2'', not ``> 1'' as would logically be expected.
	 */
	idesc->id_entryno++;
	act = -1;
	/*
	 * The obvious check would be for d_ino < UFSROOTINO.  However,
	 * 1 is a valid inode number.  Although it isn't currently used,
	 * as it was once the bad block list, there's nothing to prevent
	 * it from acquiring a new purpose in the future.  So, don't
	 * arbitrarily disallow it.  We don't test for <= zero, because
	 * d_ino is unsigned.
	 */
	update_lncntp = 0;
	if (dirp->d_ino > maxino || dirp->d_ino == 0) {
		fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE");
		act = (reply(PASS2B_PROMPT, idesc->id_number) == 1);
	} else {
again:
		update_lncntp = 0;
		switch (statemap[dirp->d_ino] & ~(INDELAYD)) {
		case USTATE:
			if (idesc->id_entryno <= 2)
				break;
			fileerror(idesc->id_number, dirp->d_ino, "UNALLOCATED");
			act = (reply(PASS2B_PROMPT, idesc->id_number) == 1);
			break;

		case DCLEAR:
		case FCLEAR:
		case SCLEAR:
			if (idesc->id_entryno <= 2)
				break;
			dp = ginode(dirp->d_ino);
			if (statemap[dirp->d_ino] == DCLEAR) {
				errmsg = ((dp->di_mode & IFMT) == IFATTRDIR) ?
			    "REFERENCE TO ZERO LENGTH ATTRIBUTE DIRECTORY" :
			    "REFERENCE TO ZERO LENGTH DIRECTORY";
				inp = getinoinfo(dirp->d_ino);
				if (inp == NULL) {
					/*
					 * The inode doesn't exist, as all
					 * should be cached by now.  This
					 * gets caught by the range check
					 * above, and so it is a can't-happen
					 * at this point.
					 */
					errexit("pass2check found a zero-len "
						"reference to bad I=%d\n",
						dirp->d_ino);
				}
				if (inp->i_parent != 0) {
					(void) printf(
		    "Multiple links to I=%d, link counts wrong, rerun fsck\n",
					    inp->i_number);
					iscorrupt = 1;
				}
			} else if (statemap[dirp->d_ino] == SCLEAR) {
				/*
				 * In theory, this is a can't-happen,
				 * because shadows don't appear in directory
				 * entries.  However, an inode might've
				 * been reused without a stale directory
				 * entry having been cleared, so check
				 * for it just in case.  We'll check for
				 * the no-dir-entry shadows in pass3b().
				 */
				errmsg = "ZERO LENGTH SHADOW";
			} else {
				errmsg = "DUP/BAD";
			}
			fileerror(idesc->id_number, dirp->d_ino, errmsg);
			if ((act = reply(PASS2B_PROMPT, idesc->id_number)) == 1)
				break;
			/*
			 * Not doing anything about it, so just try
			 * again as whatever the base type was.
			 *
			 * fileerror() invalidated dp.  Lint thinks this
			 * is unnecessary, but we know better.
			 */
			dp = ginode(dirp->d_ino);
			statemap[dirp->d_ino] &= STMASK;
			TRACK_LNCNTP(dirp->d_ino, lncntp[dirp->d_ino] = 0);
			goto again;

		case DSTATE:
		case DZLINK:
			if (statemap[idesc->id_number] == DFOUND) {
				statemap[dirp->d_ino] = DFOUND;
			}
			/* FALLTHROUGH */

		case DFOUND:
			/*
			 * This is encouraging the best-practice of not
			 * hard-linking directories.  It's legal (see POSIX),
			 * but not a good idea.  So, don't consider it an
			 * instance of corruption, but offer to nuke it.
			 */
			inp = getinoinfo(dirp->d_ino);
			if (inp == NULL) {
				/*
				 * Same can't-happen argument as in the
				 * zero-len case above.
				 */
				errexit("pass2check found bad reference to "
					"hard-linked directory I=%d\n",
					dirp->d_ino);
			}
			dp = ginode(idesc->id_number);
			if (inp->i_parent != 0 && idesc->id_entryno > 2 &&
			    ((dp->di_mode & IFMT) != IFATTRDIR)) {
				/*
				 * XXX For nested dirs, this can report
				 * the same name for both paths.
				 */
				getpathname(pathbuf, idesc->id_number,
				    dirp->d_ino);
				getpathname(namebuf, dirp->d_ino, dirp->d_ino);
				pwarn(
		    "%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s\n",
				    pathbuf, namebuf);
				if (preen)
					(void) printf(" (IGNORED)\n");
				else if ((act = reply(PASS2B_PROMPT,
				    idesc->id_number)) == 1) {
					update_lncntp = 1;
					broke_dir_link = 1;
					break;
				}
			}

			if ((idesc->id_entryno > 2) &&
					(inp->i_extattr != idesc->id_number)) {
				inp->i_parent = idesc->id_number;
			}
			/* FALLTHROUGH */

		case FSTATE:
		case FZLINK:
			/*
			 * There's nothing to do for normal file-like
			 * things.  Extended attributes come through
			 * here as well, though, and for them, .. may point
			 * to a file.  In this situation we don't want
			 * to decrement link count as it was already
			 * decremented when the entry was seen in the
			 * directory it actually lives in.
			 */
			pdirp = ginode(idesc->id_number);
			pdirtype = (pdirp->di_mode & IFMT);
			dp = ginode(dirp->d_ino);
			isattr = (dp->di_cflags & IXATTR);
			act = -1;
			if (pdirtype == IFATTRDIR &&
			    (strcmp(dirp->d_name, "..") == 0)) {
				dontreconnect = 0;
				if (dp->di_oeftflag != 0) {
					attrdirp = ginode(dp->di_oeftflag);

					/*
					 * is it really an attrdir?
					 * if so, then don't do anything.
					 */

					if ((attrdirp->di_mode & IFMT) ==
					    IFATTRDIR)
						dontreconnect = 1;
					dp = ginode(dirp->d_ino);
				}
				/*
				 * Rare corner case - the attrdir's ..
				 * points to the attrdir itself.
				 */
				if (dirp->d_ino == idesc->id_number) {
					dontreconnect = 1;
					TRACK_LNCNTP(idesc->id_number,
					    lncntp[idesc->id_number]--);
				}
				/*
				 * Lets see if we have an orphaned attrdir
				 * that thinks it belongs to this file.
				 * Only re-connect it if the current
				 * attrdir is 0 or not an attrdir.
				 */
				if ((dp->di_oeftflag != idesc->id_number) &&
				    (dontreconnect == 0)) {
					fileerror(idesc->id_number,
					    dirp->d_ino,
					    "Attribute directory I=%d not "
					    "attached to file I=%d\n",
					    idesc->id_number, dirp->d_ino);
					if ((act = reply("FIX")) == 1) {
						dp = ginode(dirp->d_ino);
						if (debug)
							(void) printf(
				    "debug: changing i=%d's oeft from %d ",
							    dirp->d_ino,
							    dp->di_oeftflag);
						dp->di_oeftflag =
						    idesc->id_number;
						if (debug)
							(void) printf("to %d\n",
							    dp->di_oeftflag);
						inodirty();
						registershadowclient(
						    idesc->id_number,
						    dirp->d_ino,
						    &attrclientinfo);
					}
					dp = ginode(dirp->d_ino);
				}

				/*
				 * This can only be true if we've modified
				 * an inode/xattr connection, and we
				 * don't keep track of those in the link
				 * counts.  So, skipping the checks just
				 * after this is not a problem.
				 */
				if (act > 0)
					return (KEEPON | ALTERED);

				/*
				 * Don't screw up link counts for directories.
				 * If we aren't careful we can perform
				 * an extra decrement, since the .. of
				 * an attrdir could be either a file or a
				 * directory.  If it's a file then its link
				 * should be correct after it is seen when the
				 * directory it lives in scanned.
				 */
				if ((pdirtype == IFATTRDIR) &&
				    ((dp->di_mode & IFMT) == IFDIR))
						breakout = 1;
				if ((dp->di_mode & IFMT) != IFDIR)
					breakout = 1;

			} else if ((pdirtype != IFATTRDIR) ||
			    (strcmp(dirp->d_name, ".") != 0)) {
				if ((pdirtype == IFDIR) && isattr) {
					fileerror(idesc->id_number,
					    dirp->d_ino,
					    "File should NOT be marked as "
					    "extended attribute\n");
					if ((act = reply("FIX")) == 1) {
						dp = ginode(dirp->d_ino);
						if (debug)
							(void) printf(
				    "changing i=%d's cflags from 0x%x to ",
							    dirp->d_ino,
							    dp->di_cflags);

						dp->di_cflags &= ~IXATTR;
						if (debug)
							(void) printf("0x%x\n",
							    dp->di_cflags);
						inodirty();
						if ((dp->di_mode & IFMT) ==
						    IFATTRDIR) {
							dp->di_mode &=
							    ~IFATTRDIR;
							dp->di_mode |= IFDIR;
							inodirty();
							pdirp = ginode(
							    idesc->id_number);
							if (pdirp->di_oeftflag
								!= 0) {
							pdirp->di_oeftflag = 0;
								inodirty();
							}
						}
					}
				} else {
					if (pdirtype == IFATTRDIR &&
					    (isattr == 0)) {
						fileerror(idesc->id_number,
						    dirp->d_ino,
						    "File should BE marked as "
						    "extended attribute\n");
						if ((act = reply("FIX")) == 1) {
							dp = ginode(
							    dirp->d_ino);
							dp->di_cflags |= IXATTR;
							/*
							 * Make sure it's a file
							 * while we're at it.
							 */
							dp->di_mode &= ~IFMT;
							dp->di_mode |= IFREG;
							inodirty();
						}
					}
				}

			}
			if (breakout == 0 || dontreconnect == 0) {
				TRACK_LNCNTP(dirp->d_ino,
				    lncntp[dirp->d_ino]--);
				if (act > 0)
					return (KEEPON | ALTERED);
			}
			break;

		case SSTATE:
			errmsg = "ACL IN DIRECTORY";
			fileerror(idesc->id_number, dirp->d_ino, errmsg);
			act = (reply(PASS2B_PROMPT, idesc->id_number) == 1);
			break;

		default:
			errexit("BAD STATE 0x%x FOR INODE I=%d",
			    statemap[dirp->d_ino], dirp->d_ino);
		}
	}

	if (act == 0) {
		iscorrupt = 1;
	}

	if (act <= 0)
		return (ret|KEEPON);

	if (update_lncntp) {
		LINK_RANGE(errmsg, lncntp[idesc->id_number], 1);
		if (errmsg != NULL) {
			LINK_CLEAR(errmsg, idesc->id_number, IFDIR, &ldesc);
			if (statemap[idesc->id_number] == USTATE) {
				idesc->id_number = 0;
				ret |= ALTERED;
			}
		}
		TRACK_LNCNTP(idesc->id_number, lncntp[idesc->id_number]++);
	}

	dirp->d_ino = 0;

	return (ret|KEEPON|ALTERED);
}

#undef	PASS2B_PROMPT

/*
 * Routine to sort disk blocks.
 */
static int
blksort(const void *arg1, const void *arg2)
{
	const struct inoinfo **inpp1 = (const struct inoinfo **)arg1;
	const struct inoinfo **inpp2 = (const struct inoinfo **)arg2;

	return ((*inpp1)->i_blks[0] - (*inpp2)->i_blks[0]);
}