/*
 * Copyright (c) 1980, 1986 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by the University of California, Berkeley.  The name of the
 * University may not 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.
 */

#include <afsconfig.h>
#include <afs/param.h>

RCSID("$Header: /cvs/openafs/src/vfsck/main.c,v 1.5.2.1 2001/12/26 20:26:57 shadow Exp $");

#define VICE	/* allow us to put our changes in at will */
#include <stdio.h>

#include <sys/param.h>
#include <sys/time.h>

#ifdef AFS_SUN_ENV
#define KERNEL
#endif /* AFS_SUN_ENV */
#include <sys/mount.h>
#ifdef AFS_SUN_ENV
#undef KERNEL
#endif

#include <sys/file.h>

#ifdef	AFS_OSF_ENV
#include <sys/vnode.h>
#include <sys/mount.h>
#include <ufs/inode.h>
#include <ufs/fs.h>
#else	/* AFS_OSF_ENV */
#ifdef AFS_VFSINCL_ENV
#define VFS
#include <sys/vnode.h>
#ifdef	  AFS_SUN5_ENV
#include <unistd.h>
#include <sys/fs/ufs_inode.h>
#include <sys/fs/ufs_fs.h>
#define _KERNEL
#include <sys/fs/ufs_fsdir.h>
#undef _KERNEL
#include <sys/fs/ufs_mount.h>
#else
#include <ufs/inode.h>
#include <ufs/fs.h>
#define KERNEL
#include <ufs/fsdir.h>
#undef KERNEL
#endif
#else /* AFS_VFSINCL_ENV */

#include <sys/inode.h>
#ifdef	AFS_HPUX_ENV
#include <ctype.h>
#define	LONGFILENAMES	1
#include <sys/sysmacros.h>
#include <sys/ino.h>
#define	DIRSIZ_MACRO
#include <ndir.h>
#else
#define KERNEL
#include <sys/dir.h>
#undef KERNEL
#endif
#include <sys/fs.h>
#endif /* AFS_VFSINCL_ENV */
#endif	/* AFS_OSF_ENV */

#ifdef AFS_DEC_ENV
#include <sys/fs_types.h>
#endif

#include <sys/stat.h>
#include <sys/wait.h>
#ifdef	AFS_SUN5_ENV
#include <string.h>
#else
#include <strings.h>
#endif
#include <ctype.h>
#ifdef	XAFS_SUN_ENV
#include <mntent.h>
#else
#ifdef	AFS_SUN5_ENV
#include <sys/mnttab.h>
#include <sys/mntent.h>
#include <sys/vfstab.h>
#include <sys/ustat.h>
#else
#include <fstab.h>
#endif
#endif	
#include "fsck.h"
#include <errno.h>
#include <sys/signal.h>

char	*rawname(), *unrawname(), *blockcheck(), *malloc();
void	catch(), catchquit(), voidquit();
static	int tryForce;
int	returntosingle;

extern int errno;

struct part {
	char	*name;			/* device name */
	char	*fsname;		/* mounted filesystem name */
	struct	part *next;		/* forward link of partitions on disk */
} *badlist, **badnext = &badlist;

struct disk {
	char	*name;			/* disk base name */
	struct	disk *next;		/* forward link for list of disks */
	struct	part *part;		/* head of list of partitions on disk */
	int	pid;			/* If != 0, pid of proc working on */
} *disks;

int	nrun, ndisks, maxrun, wflag=0;
#ifdef	AFS_HPUX_ENV
int fixed;
#endif

#if	defined(AFS_HPUX100_ENV)
#include <ustat.h>
#include <mntent.h>
#endif 

#ifdef VICE
#define	msgprintf   vfscklogprintf
#else /* VICE */
#define	msgprintf   printf
#endif /* VICE */

#ifdef	AFS_SUN5_ENV
int	mnt_passno = 0;
#endif

#include "AFS_component_version_number.c"

#ifdef	AFS_HPUX_ENV
int     ge_danger = 0;  /* on when fsck is not able to fix the dirty file 
                           system within single run. Problems like dup table
                           overflow, maxdup is exceeding MAXDUP.. etc. could
                           potentailly prevent fsck from doing a complete 
                           repair. This is found in a GE hotsite. */
#endif

main(argc, argv)
	int	argc;
	char	*argv[];
{
	struct fstab *fsp;
	int pid, passno, sumstatus;
	char *name;
	register struct disk *dk, *nextdisk;
	register struct part *pt;
	extern	 char*	AFSVersion;  /* generated version */
#ifdef	AFS_SUN5_ENV
	int other_than_ufs=0;
	char *subopt;
	struct vfstab vt;
	FILE *vfile;
	int ret;
	struct vfstab vget;
	FILE *fd;
#endif

	sync();
	tryForce = 0;
#if	defined(AFS_HPUX_ENV) 
	pclean = 0;
#endif
#if	defined(AFS_HPUX_ENV) || defined(AFS_SUN5_ENV) || defined(AFS_OSF_ENV)
	fflag = 0;
#endif
#ifdef	AFS_SUN5_ENV
	fsflag = oflag = mflag = exitstat = 0;
#endif
#if	defined(AFS_HPUX100_ENV)
	mflag = 0;
#endif
	printf("----Open AFS (R) %s fsck----\n", AFSVersion); /* generated version */
	if (access("/TRYFORCE", 0) == 0)
	    tryForce = 1;
	while (--argc > 0 && **++argv == '-') {
		switch (*++*argv) {

#if	defined(AFS_HPUX_ENV) 
#if	defined(AFS_HPUX100_ENV)
		case 'f':	/* default yes to answer force to check */
		    fflag++;
		    break;
#else  /* AFS_HPUX100_ENV */
#ifdef	AFS_HPUX_ENV
		case 'F':	/* default yes to answer force to check */
		    fflag++;
		    break;
#endif /* AFS_HPUX_ENV */
#endif /* AFS_HPUX100_ENV */
		case 'P':
		        pclean++;
			preen++;
			break;		    
#endif
#if	defined(AFS_DEC_ENV)
		    /* On the late versions of Ultrix they changed the defn of '-p' a bit. Particularly,
		     * -p is used to check a file system that was not unmounted cleanly, and they added,
		     * -P to check a file system regardless of how it was unmounted; this, unfortunately,
		     * is identical to '-p' on the rest of the systems but we have to maintain vendor's
		     * semantics so we leave it the way Dec likes it.
		     */
	        case 'p':
		    only_when_needed++;
		/* P is for Ultrix compatibility */
		case 'P':
#else
		case 'p':
#endif
			preen++;
			break;
#if	defined(AFS_HPUX100_ENV)
                case 'V':
                    {
                    int opt_count;
                    char        *opt_text;

                    (void) fprintf (stdout, "fsck -F hfs ");
                    for (opt_count = 1; opt_count < argc ; opt_count++) {
                        opt_text = argv[opt_count];
                        if (opt_text)
                            (void) fprintf (stdout, " %s ", opt_text);
                    }
                    (void) fprintf (stdout, "\n");
		    exit(0);
                    }
                    break;
		case 'm':
		    mflag++;
		    break;
#endif
#ifdef	AFS_SUN5_ENV
		case 'V':
		    {
		    int	opt_count;
		    char	*opt_text;

		    (void) fprintf (stdout, "fsck -F ufs ");
		    for (opt_count = 1; opt_count < argc ; opt_count++) {
			opt_text = argv[opt_count];
			if (opt_text)
			    (void) fprintf (stdout, " %s ", opt_text);
		    }
		    (void) fprintf (stdout, "\n");
		    }
		    break;

		case 'o':
		    subopt = *++argv;
		    argc--;
		    while (*subopt != '\0') {
			if (*subopt == 'p') {
			    preen++;
			    break;
			} else if (*subopt == 'b') {
			    if (argv[0][1] != '\0') {
				bflag = atoi(argv[0]+1);
			    } else {
				bflag = atoi(*++argv);
				argc--;
			    }
			    msgprintf("Alternate super block location: %d\n", bflag);
			    break;			    
			} else if (*subopt == 'd') {
			    debug++;
			    break;
			} else if (*subopt == 'r') {
			    break;
			} else if (*subopt == 'w') {
			    wflag++;
			    break;
			} else if (*subopt == 'c') {
			    cvtflag++;
			    break;
			} else if (*subopt == 'f') {
			    fflag++;
			    break;			    
			} else {
			    errexit("-o %c option?\n", *subopt);
			}
			subopt++;
			++argv;
			argc--;
		    }
		    oflag++;
		    break;
		case 'm':
		    mflag++;
		    break;
#else
		case 'b':
			if (argv[0][1] != '\0') {
				bflag = atoi(argv[0]+1);
			} else {
				bflag = atoi(*++argv);
				argc--;
			}
			msgprintf("Alternate super block location: %d\n", bflag);
			break;

		case 'c':
			cvtflag++;
			break;

			/* who knows?  defined, but doesn't do much */
		case 'r':
			break;

		case 'w':	/* check writable only */
			wflag++;
			break;
		case 'd':
			debug++;
			break;
		case 'l':
			if (!isdigit(argv[1][0]))
				errexit("-l flag requires a number\n");
			maxrun = atoi(*++argv);
			argc--;
			break;
#if	!defined(AFS_HPUX100_ENV)
		case 'm':
			if (!isdigit(argv[1][0]))
				errexit("-m flag requires a mode\n");
			sscanf(*++argv, "%o", &lfmode);
			if (lfmode &~ 07777)
				errexit("bad mode to -m: %o\n", lfmode);
			argc--;
			printf("** lost+found creation mode %o\n", lfmode);
			break;
#endif /* AFS_HPUX100_ENV */
#endif	/* AFS_SUN5_ENV */
#ifdef	AFS_OSF_ENV
		case 'o':
			fflag++;
			break;
#endif	/* AFS_OSF_ENV */
		case 'n':
		case 'N':
			nflag++;
			yflag = 0;
			break;

		/*
		 * NOTE: -q flag is used only by HPux fsck versions but we add it for all systems since
		 * it's general/useful flag to use.
		 */
		case 'q':
			qflag++;
			break;

		case 'y':
		case 'Y':
			yflag++;
			nflag = 0;
			break;

		default:
			errexit("%c option?\n", **argv);
		}
	}
	/*
	 * The following checks were only available on hpux but are useful to all systems.
	 */
	if (nflag && preen)
		errexit("Incompatible options: -n and -p\n");
	if (nflag && qflag)
		errexit("Incompatible options: -n and -q\n");

#ifdef	AFS_SUN5_ENV
	rflag++; 	/* check raw devices */
#endif
	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
		(void)signal(SIGINT, catch);
	if (preen)
		(void)signal(SIGQUIT, catchquit);
	if (argc) {
		while (argc-- > 0) {
			hotroot = 0;
#ifdef	AFS_SUN5_ENV
			if (wflag && !writable(*argv)) {
				(void) fprintf (stderr, "not writeable '%s'\n", *argv);
				argv++;
			} else
#endif
			checkfilesys(*argv++, (char *)0);
		}
#ifdef	AFS_HPUX_ENV
                if (ge_danger)
                        exit(-1);
#endif
#ifdef	AFS_SUN5_ENV
		exit(exitstat);
#else
		exit(0);
#endif
	}
#ifndef	AFS_SUN5_ENV
	sumstatus = 0;
#ifdef	AFS_SUN5_ENV
	if (fstype == NULL || strcmp(fstype, MNTTYPE_UFS) == 0) {
	    int status;

	    if ((fd = fopen(VFSTAB, "r")) == NULL) {
		errexit("vfsck: cannot open vfstab\n");
	    }
	    while ((ret = getvfsent(fd, &vget)) == 0) {
		if (strcmp(vget.vfs_fstype, MNTTYPE_UFS) &&  numbers(vget.vfs_fsckpass)) {
		    other_than_ufs++;
		    continue;
		}
		if (numbers(vget.vfs_fsckpass))
		    passno = atoi(vget.vfs_fsckpass);
		else
		    continue;
		if (passno < 1)
		    continue;
		if (preen == 0 || passno == 1) {
		    checkfilesys(vget.vfs_fsckdev, get.vfs_mountp);
		} else if (passno > 1) {
		    addpart(vget.vfs_fsckdev, vget.vfs_special);
		}
	    }
#else
	for (passno = 1; passno <= 2; passno++) {
	    if (setfsent() == 0)
		errexit("Can't open checklist file: %s\n", FSTAB);
	    while ((fsp = getfsent()) != 0) {
		if (strcmp(fsp->fs_type, FSTAB_RW) &&
		    strcmp(fsp->fs_type, FSTAB_RO) &&
		    strcmp(fsp->fs_type, FSTAB_RQ))
		    continue;
#ifdef	AFS_DEC_ENV
			/* Only check local (i.e. ufs) file systems */
			if (strcmp(fsp->fs_name, "4.2") && strcmp(fsp->fs_name, "ufs"))
			  continue;
#endif
#ifdef	AFS_OSF_ENV
		if (strcmp(fsp->fs_vfstype, "ufs") ||
		    fsp->fs_passno == 0) {
		    continue;
		}
#endif	/* AFS_OSF_ENV */
			if (preen == 0 || passno == 1 && fsp->fs_passno == 1) {
			    if (passno == 1) {
				name = blockcheck(fsp->fs_spec);
				if (name != NULL) {
					checkfilesys(name, fsp->fs_file);
				} else if (preen) {
				    printf("pid %d exiting 8/1\n", getpid());
				    exit(8);
				}
			    }
			} else if (passno == 2 && fsp->fs_passno > 1) {
				name = blockcheck(fsp->fs_spec);
				if (name == NULL) {
					pwarn("BAD DISK NAME %s\n",
						fsp->fs_spec);
					sumstatus |= 8;
					printf("pid %d saw bad disk name 8/3\n", getpid());
					continue;
				}
				addpart(name, fsp->fs_file);
			}
		}
#endif	AFS_SUN5_ENV
	}
	if (preen) {
		int status, rc;

		if (maxrun == 0)
			maxrun = ndisks;
		if (maxrun > ndisks)
			maxrun = ndisks;
		nextdisk = disks;
		for (passno = 0; passno < maxrun; ++passno) {
			startdisk(nextdisk);
			nextdisk = nextdisk->next;
		}
		while ((pid = wait(&status)) != -1) {
			for (dk = disks; dk; dk = dk->next)
				if (dk->pid == pid)
					break;
			if (dk == 0) {
				printf("Unknown pid %d\n", pid);
				continue;
			}
			rc = WEXITSTATUS(status);
			if (WIFSIGNALED(status)) {
				printf("%s (%s): EXITED WITH SIGNAL %d\n",
					dk->part->name, dk->part->fsname,
					WTERMSIG(status));
				rc = 8;
			}
			if (rc != 0) {
				sumstatus |= rc;
				*badnext = dk->part;
				badnext = &dk->part->next;
				dk->part = dk->part->next;
				*badnext = NULL;
			} else
				dk->part = dk->part->next;
			dk->pid = 0;
			nrun--;
			if (dk->part == NULL)
				ndisks--;

			if (nextdisk == NULL) {
				if (dk->part)
					startdisk(dk);
			} else if (nrun < maxrun && nrun < ndisks) {
				for ( ;; ) {
					if ((nextdisk = nextdisk->next) == NULL)
						nextdisk = disks;
					if (nextdisk->part != NULL &&
					    nextdisk->pid == 0)
						break;
				}
				startdisk(nextdisk);
			}
		}
	}
	if (sumstatus) {
		if (badlist == 0) {
		    printf("pid %d exiting 8/2\n", getpid());
		    exit(8);
		}
		printf("THE FOLLOWING FILE SYSTEM%s HAD AN %s\n\t",
			badlist->next ? "S" : "", "UNEXPECTED INCONSISTENCY:");
		for (pt = badlist; pt; pt = pt->next)
			printf("%s (%s)%s", pt->name, pt->fsname,
			    pt->next ? ", " : "\n");
		exit(8);
	}
#ifdef	AFS_SUN5_ENV
	fclose(fd);
#else
	(void)endfsent();
#endif
	if (returntosingle)
		exit(2);
#endif	/* !AFS_SUN5_ENV */
	exit(0);
}

struct disk *
finddisk(name)
	char *name;
{
	register struct disk *dk, **dkp;
	register char *p;
	int len;

	for (p = name + strlen(name) - 1; p >= name; --p)
		if (isdigit(*p)) {
			len = p - name + 1;
			break;
		}
	if (p < name)
		len = strlen(name);

	for (dk = disks, dkp = &disks; dk; dkp = &dk->next, dk = dk->next) {
		if (strncmp(dk->name, name, len) == 0 &&
		    dk->name[len] == 0)
			return (dk);
	}
	if ((*dkp = (struct disk *)malloc(sizeof(struct disk))) == NULL)
		errexit("out of memory");
	dk = *dkp;
	if ((dk->name = malloc((unsigned int)len + 1)) == NULL)
		errexit("out of memory");
	strncpy(dk->name, name, len);
	dk->name[len] = '\0';
	dk->part = NULL;
	dk->next = NULL;
	dk->pid = 0;
	ndisks++;
	return (dk);
}

addpart(name, fsname)
	char *name, *fsname;
{
	struct disk *dk = finddisk(name);
	register struct part *pt, **ppt = &dk->part;

	for (pt = dk->part; pt; ppt = &pt->next, pt = pt->next)
		if (strcmp(pt->name, name) == 0) {
			printf("%s in fstab more than once!\n", name);
			return;
		}
	if ((*ppt = (struct part *)malloc(sizeof(struct part))) == NULL)
		errexit("out of memory");
	pt = *ppt;
	if ((pt->name = malloc((unsigned int)strlen(name) + 1)) == NULL)
		errexit("out of memory");
	strcpy(pt->name, name);
	if ((pt->fsname = malloc((unsigned int)strlen(fsname) + 1)) == NULL)
		errexit("out of memory");
	strcpy(pt->fsname, fsname);
	pt->next = NULL;
}

startdisk(dk)
	register struct disk *dk;
{

	nrun++;
	dk->pid = fork();
	if (dk->pid < 0) {
		perror("fork");
		exit(8);
	}
	if (dk->pid == 0) {
		(void)signal(SIGQUIT, voidquit);
		checkfilesys(dk->part->name, dk->part->fsname);
		exit(0);
	}
}

checkfilesys(filesys, parname)
	char *filesys;
{
	daddr_t n_ffree, n_bfree;
	struct dups *dp;
	struct stat tstat;	/* for ultrix 3 unmount */
	struct zlncnt *zlnp;
	char devbuffer[128];
	int ret_val;
#ifdef	AFS_DEC_ENV
	int retries = 3;	/* # of retries fora clean fsck pass */
	int fsdirty = 0;	/* file system was or is dirty */
	int rootdirty = 0;	/* Root was or is dirty */
#endif

#ifdef	AFS_OSF_ENV
	int temp;
#endif	/* AFS_OSF_ENV */

#ifdef	AFS_SUN_ENV
	iscorrupt = 1;
#endif
#ifdef	AFS_SUN5_ENV
	mountedfs = 0;
	isconvert = 0;
#endif
#ifdef	AFS_HPUX_ENV
        ge_danger = 0;          /* set to 1 by any table overflow or more 
                                   dup/bad blocks than expected */

	fixed = 1;			/* set to 0 by any 'no' reply */
#endif
	strcpy(devbuffer, filesys);	/* copy the file system name to the device buffer */
	devname = devbuffer;		/* remember generic ptr for later */
	EnsureDevice(devname);		/* canonicalize name */
	if (debug && preen)
		pinfo("starting\n");
#ifdef	AFS_DEC_ENV
	for (; retries > 0; retries--) {	/* 003 - Loop for NUMRETRIES or until clean */
#endif

	ret_val = setup(devname);

	if (ret_val == 0) {
#ifdef	AFS_SUN_ENV
		if (iscorrupt == 0)
			return;	    
#endif
		if (preen)
			pfatal("CAN'T CHECK FILE SYSTEM.");
#ifdef	AFS_SUN5_ENV
		if ((exitstat == 0) && (mflag))
			exitstat = 32;
		exit(exitstat);
#endif
		return (0);
#ifdef	AFS_HPUX_ENV
	}  else if (ret_val == -1 ) {	/* pclean && FS_CLEAN */
		return(1);
#endif
#if	defined(AFS_DEC_ENV) || defined(AFS_OSF_ENV)
	}  else if (ret_val == FS_CLEAN) { /* pclean && FS_CLEAN */
	    return(1);
#endif
	}	
#if	defined(AFS_HPUX100_ENV)
	if (mflag)
	    check_sanity(filesys);
#endif

#ifdef	AFS_SUN5_ENV
	if (mflag)
	    check_sanity(filesys);
	if (debug)
	    printclean();
#endif
#ifdef	AFS_SUN_ENV
	iscorrupt = 0;
#endif
	/*
	 * 1: scan inodes tallying blocks used
	 */
	if (preen == 0) {
#if	defined(AFS_SUN5_ENV)
		if (mountedfs)
			msgprintf("** Currently Mounted on %s\n", sblock.fs_fsmnt);
		else
#endif
		msgprintf("** Last Mounted on %s\n", sblock.fs_fsmnt);
		if (hotroot)
			msgprintf("** Root file system\n");
#ifdef	AFS_SUN5_ENV
		if (mflag) {
			printf("** Phase 1 - Sanity Check only\n");
			return;
		} else
#endif
		msgprintf("** Phase 1 - Check Blocks and Sizes\n");
	}
	pass1();

	/*
	 * 1b: locate first references to duplicates, if any
	 */
	if (duplist) {
		if (preen)
			pfatal("INTERNAL ERROR: dups with -p");
		msgprintf("** Phase 1b - Rescan For More DUPS\n");
		pass1b();
	}

	/*
	 * 2: traverse directories from root to mark all connected directories
	 */
	if (preen == 0)
		msgprintf("** Phase 2 - Check Pathnames\n");
	pass2();

	/*
	 * 3: scan inodes looking for disconnected directories
	 */
	if (preen == 0)
		msgprintf("** Phase 3 - Check Connectivity\n");
	pass3();

	/*
	 * 4: scan inodes looking for disconnected files; check reference counts
	 */
	if (preen == 0)
		msgprintf("** Phase 4 - Check Reference Counts\n");
	pass4();

	/*
	 * 5: check and repair resource counts in cylinder groups
	 */
	if (preen == 0)
		msgprintf("** Phase 5 - Check Cyl groups\n");
	pass5();

#ifdef	AFS_DEC_ENV
	if (fsmodified || (sblk.b_dirty) || (cgblk.b_dirty)) {
	    fsdirty = 1;
	    if (hotroot)
		rootdirty = 1;
	    if (retries <= 1) {
		/*
		 * 003 - Number of retry attempts have been
		 * exhausted. Mark super block as dirty.
		 */
		(void)time(&sblock.fs_time);
		sbdirty();
	    }
	} else {
	    /*
	     * 003 - If checking root file system, and it was
	     * modified during any pass, don't assume it is ok. Must reboot.
	     */
	    if (rootdirty) {
		sbdirty();
		retries = 0;
	    } else {
		if ((!hotroot) && (!bflag) && (!nflag) && (!iscorrupt)) {
		    sblock.fs_fmod = 0;
		    sblock.fs_clean = FS_CLEAN;
		    (void)time(&sblock.fs_time);
		    (void)time(&sblock.fs_lastfsck);
		    if ((sblock.fs_deftimer) && (sblock.fs_deftimer > 0) && (sblock.fs_deftimer < 255))
			sblock.fs_cleantimer = sblock.fs_deftimer;
		    else
			sblock.fs_cleantimer = 	sblock.fs_deftimer = FSCLEAN_TIMEOUTFACTOR;
		    sbdirty();
		}
		/*
		 * 006 - If an alternate super block was used,
		 * we want to re fsck the partition after 
		 * updating the primary super block.
		 */
		if (!bflag)
		    retries = 0;
	    }
	}
	/* Unless no updates are to be done, write out maps. */
	if (nflag)
	    retries = 0;
	else
	    ckfini();
	if (debug) {
	    daddr_t nn_files = n_files;
	    daddr_t nn_blks = n_blks;

	    n_ffree = sblock.fs_cstotal.cs_nffree;
	    n_bfree = sblock.fs_cstotal.cs_nbfree;
	    if (nn_files -= maxino - ROOTINO - sblock.fs_cstotal.cs_nifree)
		printf("%d files missing\n", nn_files);
	    nn_blks += sblock.fs_ncg * (cgdmin(&sblock, 0) - cgsblock(&sblock, 0));
	    nn_blks += cgsblock(&sblock, 0) - cgbase(&sblock, 0);
	    nn_blks += howmany(sblock.fs_cssize, sblock.fs_fsize);
	    if (nn_blks -= maxfsblock - (n_ffree + sblock.fs_frag * n_bfree))
		printf("%d blocks missing\n", nn_blks);
	}
	if (duplist != NULL) {
	    if (debug) printf("The following duplicate blocks remain:");
	    for (dp = duplist; dp; dp = dp->next) {
		if (debug)
		    printf(" %d,", dp->dup);
		free(dp);
	    }
	    if (debug)
		printf("\n");
	}
	if (zlnhead != NULL) {
	    if (debug)
		printf("The following zero link count inodes remain:");
	    for (zlnp = zlnhead; zlnp; zlnp = zlnp->next) {
		if (debug)
		    printf(" %d,", zlnp->zlncnt);
		free(zlnp);
	    }
	    if (debug)
		printf("\n");
	}
	zlnhead = (struct zlncnt *)0;
	duplist = (struct dups *)0;

	free(blockmap);
	free(statemap);
	free((char *)lncntp);
	/* Print out retry message, and fsck file system again. */
	if (retries > 1)
	    if (preen)		
		printf("%s: FILE SYSTEM MODIFIED, VERIFYING\n",filesys);
	    else
		printf("**** FILE SYSTEM MODIFIED, VERIFYING\n");
	} /* for retries */
#endif

#if	defined(AFS_SUN_ENV) && !defined(AFS_SUN3_ENV)
	updateclean();
	if (debug)
		printclean();
#endif
	/*
	 * print out summary statistics
	 */
	n_ffree = sblock.fs_cstotal.cs_nffree;
	n_bfree = sblock.fs_cstotal.cs_nbfree;
#ifdef VICE
#if defined(ACLS) && defined(AFS_HPUX_ENV)
	pinfo("%d files, %d icont, %d used, %d free",
	    n_files, n_cont, n_blks, n_ffree + sblock.fs_frag * n_bfree);
#else
	pinfo("%d files, %d used, %d free",
	    n_files, n_blks, n_ffree + sblock.fs_frag * n_bfree);
#endif
	if (nViceFiles)
	    msgprintf(", %d AFS files", nViceFiles);
	msgprintf(" (%d frags, %d blocks, %.1f%% fragmentation)\n",
	    n_ffree, n_bfree, (float)(n_ffree * 100) / sblock.fs_dsize);
#else /* VICE */
#if defined(ACLS) && defined(AFS_HPUX_ENV)
	pinfo("%d files, %d icont, %d used, %d free ",
	    n_files, n_cont, n_blks, n_ffree + sblock.fs_frag * n_bfree);
#else
	pinfo("%d files, %d used, %d free ",
	    n_files, n_blks, n_ffree + sblock.fs_frag * n_bfree);
#endif
n	printf("(%d frags, %d blocks, %.1f%% fragmentation)\n",
	    n_ffree, n_bfree, (float)(n_ffree * 100) / sblock.fs_dsize);
#endif /* VICE */
#ifdef	AFS_DEC_ENV
	if ((!fsdirty) && (!rootdirty))
		return;
	if (!preen) {
		printf("\n***** FILE SYSTEM WAS MODIFIED *****\n");
		if (hotroot)
			printf("\n***** HALT PROCESSOR WITHOUT SYNCING DISK *****\n");
	}
	if (hotroot) {
		sync();
		exit(4);
	}
#else
	if (debug &&
	    (n_files -= maxino - ROOTINO - sblock.fs_cstotal.cs_nifree))
		msgprintf("%d files missing\n", n_files);
	if (debug) {
		n_blks += sblock.fs_ncg *
			(cgdmin(&sblock, 0) - cgsblock(&sblock, 0));
		n_blks += cgsblock(&sblock, 0) - cgbase(&sblock, 0);
		n_blks += howmany(sblock.fs_cssize, sblock.fs_fsize);
		if (n_blks -= maxfsblock - (n_ffree + sblock.fs_frag * n_bfree))
			printf("%d blocks missing\n", n_blks);
		if (duplist != NULL) {
			msgprintf("The following duplicate blocks remain:");
			for (dp = duplist; dp; dp = dp->next)
				msgprintf(" %d,", dp->dup);
			msgprintf("\n");
		}
		if (zlnhead != NULL) {
			msgprintf("The following zero link count inodes remain:");
			for (zlnp = zlnhead; zlnp; zlnp = zlnp->next)
				msgprintf(" %d,", zlnp->zlncnt);
			msgprintf("\n");
		}
	}
#endif
#ifdef	AFS_HPUX_ENV
	/* if user's specification denotes that the file system block
         * is going to be modified (nflag == 0) then fsck store the
         * correct magic number in the super block if it is not already
 	 * there
	 */
	if (!nflag && !(fswritefd < 0)) {
	    if (ge_danger) {
		printf("***** FILE SYSTEM IS NOT CLEAN, FSCK AGAIN *****\n");
		fsmodified++;
	    } else {
		if (!hotroot) {
		    if (fixed && (sblock.fs_clean != FS_CLEAN)) {
			if (!preen && !qflag)
			    printf("***** MARKING FILE SYSTEM CLEAN *****\n");
			sblock.fs_clean = FS_CLEAN;
			fsmodified++;
		    }
		} else {
		    /* fix FS_CLEAN if changes made and no 'no' replies */
		    if (fsmodified && fixed)
			sblock.fs_clean = FS_CLEAN;
		    /*
		     *  Fix fs_clean if there were no 'no' replies.
		     *  This is done for both the s300 and s800.  The s800 root will be 
		     *  guaranteed clean as of 7.0.
		     */
		    if (fixed && (sblock.fs_clean != FS_OK)) {
			if (!preen && !qflag)
			    printf("***** MARKING FILE SYSTEM CLEAN *****\n");
			sblock.fs_clean = FS_CLEAN;
			fsmodified++;
		    }
		}
	    }
	}
#endif
	zlnhead = (struct zlncnt *)0;
	duplist = (struct dups *)0;

#if	defined(AFS_SUN_ENV) && !defined(AFS_SUN3_ENV)		/* WAS AFS_SUN5_ENV */
#ifdef	notdef
	inocleanup();
#endif
	if (fsmodified)
		fixstate = 1;
	else
		fixstate = 0;
	if (hotroot && sblock.fs_clean == FSACTIVE)
		rebflg = 1;
#ifdef	AFS_SUN5_ENV
	else if (!((sblock.fs_state + (afs_int32)sblock.fs_time == FSOKAY) &&
#else
	else if (!((fs_get_state(&sblock) + (afs_int32)sblock.fs_time == FSOKAY) &&
#endif
		(sblock.fs_clean ==FSCLEAN || sblock.fs_clean ==FSSTABLE))) {
		if (yflag || !iscorrupt) {
			printf("%s FILE SYSTEM STATE SET TO OKAY\n",
				devname);
			fixstate = 1;
		} else {
			printf("%s FILE SYSTEM STATE NOT SET TO OKAY\n",
				devname);
			fixstate = 0;
		}
	}
	if (fixstate) {
		(void)time(&sblock.fs_time);
		if (!iscorrupt) {
			if (hotroot && rebflg)
				sblock.fs_clean = FSACTIVE;
			else
				sblock.fs_clean = FSSTABLE;
#ifdef	AFS_SUN5_ENV
			sblock.fs_state = FSOKAY - (afs_int32)sblock.fs_time;
#else
			fs_set_state(&sblock, FSOKAY - (afs_int32)sblock.fs_time);
#endif
		}
		sbdirty();
	}
#else
#ifdef	AFS_OSF_ENV
	if (!nflag && !bflag && !hotroot) {
	    temp = fsmodified;
	    sblock.fs_clean = FS_CLEAN;
	    (void)time(&sblock.fs_time);
	    sbdirty();
	    flush(fswritefd, &sblk);
	    fsmodified = temp;
	}
#else	/* AFS_OSF_ENV */
	if (fsmodified) {
		(void)time(&sblock.fs_time);
		sbdirty();
	}
#endif
#endif
#ifndef	AFS_DEC_ENV
	ckfini();
	free(blockmap);
	free(statemap);
	free((char *)lncntp);
	lncntp = NULL;
	blockmap = statemap = NULL;
#ifdef	AFS_SUN5_ENV
	if (iscorrupt)
	    exitstat = 36;	
#endif
	if (!fsmodified)
		return;
	if (!preen) {
		msgprintf("\n***** FILE SYSTEM WAS MODIFIED *****\n");

		if (hotroot)
			msgprintf("\n***** REBOOT UNIX *****\n");
	}
#ifdef	AFS_SUN5_ENV
 	if (mountedfs || hotroot) {
	    exitstat = 40;
	}
#endif
	if (hotroot) {
		sync();
#ifdef	AFS_HPUX_ENV
                if (ge_danger)
                        exit(-1);
                else
#endif
#ifdef  AFS_SUN5_ENV
		    exit(exitstat);
#else
		exit(4);
#endif
	}
#endif
#ifdef VICE
	(void)close(fsreadfd);
	(void)close(fswritefd);
	if (nViceFiles || tryForce) {
	    /* Modified file system with vice files: force full salvage */
	    /* Salvager recognizes the file FORCESALVAGE in the root of each partition */
#if !defined(AFS_DEC_ENV)
	    struct ufs_args ufsargs;
#endif 

	    char pname[100], fname[100], *special;
	    int fd, code, failed=0;

	    msgprintf("%s: AFS file system partition was modified; forcing full salvage\n", devname);
	    devname = unrawname(devname);
	    special = (char *) strrchr(devname, '/');
	    if (!special++) special = devname;
	    strcpy(pname, "/etc/vfsck."); /* Using /etc, rather than /tmp, since
		/tmp is a link to /usr/tmp on some systems, and isn't mounted now */
	    strcat(pname, special);
#ifdef AFS_SUN_ENV
	    /* if system mounted / as read-only, we'll try to fix now */
	    if (access("/", W_OK) < 0 && errno == EROFS) {
		code = system("mount -o remount /");
		if (code) {
		    printf("Couldn't remount / R/W; continuing anyway (%d).\n", errno);
		    failed = 1;
		}
	    }
#endif
#ifdef	AFS_OSF_ENV
	    /* if system mounted / as read-only, we'll try to fix now */
	    if (access("/", W_OK) < 0 && errno == EROFS) {
		printf("Can't RW acceess /; %d\n", errno);
		code = system("/sbin/mount -u /");
		if (code) {
		    printf("Couldn't remount / R/W; continuing anyway (%d).\n", errno);
		    failed = 1;
		}
	    }
#endif
	    rmdir(pname);
	    unlink(pname);
	    if (mkdir(pname, 0777) < 0) {
		if (errno != EEXIST) {
		    perror("fsck mkdir");
		    failed = 1;
		}
	    }
	    if (failed && parname) {
		strcpy(pname, parname);
	    }
#if !defined(AFS_DEC_ENV) && !defined(AFS_HPUX_ENV)
#ifdef	AFS_SUN5_ENV
	    ufsargs.flags = UFSMNT_NOINTR;
#else
	    ufsargs.fspec = devname;
#endif
#ifdef AFS_SUN_ENV
#ifdef	AFS_SUN5_ENV
	    if (mount(devname, pname, MS_DATA, "ufs", (char *)&ufsargs, sizeof(ufsargs)) < 0) {
#else
	    if (mount("4.2", pname, M_NEWTYPE, &ufsargs) < 0) {
#endif
#else
	    if (mount(MOUNT_UFS, pname, 0, &ufsargs) < 0) {
#endif
#else 
#ifdef AFS_DEC_ENV
	    if (mount(devname, pname, 0, GT_ULTRIX, (char *) 0)) {
#else 
	    if (mount(devname, pname, 0) < 0) {
#endif
#endif		
	        printf("Couldn't mount %s on %s to force FULL SALVAGE; continuing anyway (%d)!\n", devname, pname, errno);
	    } else {
		strcpy(fname, pname);
		strcat(fname, "/FORCESALVAGE");
		fd = open(fname, O_CREAT, 0);
		if (fd == -1) {
		    errexit("Couldn't create %s to force full salvage!\n", fname);
#if defined(AFS_DEC_ENV)
		    stat(".", &tstat);
#endif
		} else {
		    fstat(fd, &tstat);
		    close(fd);
		}
#if /*defined(AFS_VFS_ENV) &&*/ !defined(AFS_DEC_ENV) && !defined(AFS_HPUX_ENV) && !defined(AFS_SUN5_ENV) && !defined(AFS_OSF_ENV)
		unmount(pname);
#else
#if defined(AFS_DEC_ENV)
		umount(tstat.st_dev);
#else
#if	defined(AFS_OSF_ENV)
		umount(pname, MNT_NOFORCE);
#else	/* AFS_OSF_ENV */
		umount(devname);
#endif
#endif
#endif
	    }
	    rmdir(pname);
	}
	if (logfile) {
	    fsync(fileno(logfile)); /* Since update isn't running */
	    fclose(logfile);
	    logfile = 0;
	}
#endif /* VICE */

}

char *
blockcheck(name)
	char *name;
{
	struct stat stslash, stblock, stchar;
	char *raw;
	int retried = 0;

	hotroot = 0;
	if (stat("/", &stslash) < 0) {
		perror("/");
		printf("Can't stat root\n");
		return (0);
	}
retry:
	if (stat(name, &stblock) < 0) {
		perror(name);
		printf("Can't stat %s\n", name);
		return (0);
	}
	if ((stblock.st_mode & S_IFMT) == S_IFBLK) {
		if (stslash.st_dev == stblock.st_rdev) {
			hotroot++;
#if	!defined(AFS_OSF_ENV)		/*  OSF/1 always uses the raw device, even for / */
			return (name);
#endif	/* AFS_OSF_ENV */
		}
		raw = rawname(name);
		if (raw) {
		   return (raw);
		} else {
		   printf("Cannot find character device for %s\n", name);
		   return (name);
		}
	} else if ((stblock.st_mode & S_IFMT) == S_IFCHR && !retried) {
		name = unrawname(name);
		retried++;
		goto retry;
	}
	printf("Can't make sense out of name %s\n", name);
	return (0);
}


#if	defined(AFS_HPUX_ENV) || defined(AFS_SUN5_ENV)

#ifdef	AFS_SUN5_ENV
/*
 * exit 0 - file system is unmounted and okay
 * exit 32 - file system is unmounted and needs checking
 * exit 33 - file system is mounted for root file system
 * exit 34 - cannot stat device
 */
check_sanity(filename)
char	*filename;
{
    struct stat stbd, stbr;
    struct ustat usb;
    char *devname;
    struct vfstab vfsbuf;
    FILE *vfstab;
    int is_root = 0;
    int is_usr = 0;
    int is_block = 0;

    if (stat(filename, &stbd) < 0) {
	fprintf(stderr, "ufs fsck: sanity check failed : cannot stat %s\n", filename);
	exit(34);
    }

    if ((stbd.st_mode & S_IFMT) == S_IFBLK) 
	is_block = 1;
    else if ((stbd.st_mode & S_IFMT) == S_IFCHR) 
	is_block = 0;
    else {
	fprintf(stderr, "ufs fsck: sanity check failed: %s not block or character device\n", filename);
	exit(34);
    }
    /*
     * Determine if this is the root file system via vfstab. Give up
     * silently on failures. The whole point of this is not to care
     * if the root file system is already mounted.
     *
     * XXX - similar for /usr. This should be fixed to simply return
     * a new code indicating, mounted and needs to be checked.
     */
    if ((vfstab = fopen(VFSTAB, "r")) != 0) {
	if (getvfsfile(vfstab, &vfsbuf, "/") == 0) {
	    if (is_block)
		devname = vfsbuf.vfs_special;
	    else
		devname = vfsbuf.vfs_fsckdev;
	    if (stat(devname, &stbr) == 0)
		if (stbr.st_rdev == stbd.st_rdev)
		    is_root = 1;
	}
	if (getvfsfile(vfstab, &vfsbuf, "/usr") == 0) {
	    if (is_block)
		devname = vfsbuf.vfs_special;
	    else
		devname = vfsbuf.vfs_fsckdev;
	    if (stat(devname, &stbr) == 0)
		if (stbr.st_rdev == stbd.st_rdev)
		    is_usr = 1;
	}
    }

    /* XXX - only works if filename is a block device or if
       character and block device has the same dev_t value */
    if (is_root == 0 && is_usr == 0 && ustat(stbd.st_rdev, &usb) == 0) {
	fprintf(stderr, "ufs fsck: sanity check: %s already mounted\n", filename);
	exit(33);
    }
    /*
     * We mount the ufs root file system read-only first.  After fsck
     * runs, we remount the root as read-write.  Therefore, we no longer
     * check for different values for fs_state between the root file 
     * system and the rest of file systems.
     */
    if (!((sblock.fs_state + (time_t)sblock.fs_time == FSOKAY) &&
	  (sblock.fs_clean ==FSCLEAN || sblock.fs_clean ==FSSTABLE))) {
	fprintf(stderr, "ufs fsck: sanity check: %s needs checking\n", filename);
	exit(32);
    }
    fprintf(stderr, "ufs fsck: sanity check: %s okay\n", filename);
    exit(0);
}
#endif

#if	defined(AFS_HPUX100_ENV)
check_sanity(filename)
char	*filename;
{
    struct stat stbd, stbr;
    struct ustat usb;
    char *devname;
    FILE *vfstab;
    struct mntent *mnt;
    int is_root = 0;
    int is_usr = 0;
    int is_block = 0;

    if (stat(filename, &stbd) < 0) {
	fprintf(stderr, "hfs fsck: sanity check failed : cannot stat %s\n", filename);
	exit(34);
    }

    if ((stbd.st_mode & S_IFMT) == S_IFBLK) 
	is_block = 1;
    else if ((stbd.st_mode & S_IFMT) == S_IFCHR) 
	is_block = 0;
    else {
	fprintf(stderr, "hfs fsck: sanity check failed: %s not block or character device\n", filename);
	exit(34);
    }
    /*
     * Determine if this is the root file system via vfstab. Give up
     * silently on failures. The whole point of this is not to care
     * if the root file system is already mounted.
     *
     * XXX - similar for /usr. This should be fixed to simply return
     * a new code indicating, mounted and needs to be checked.
     */
    if ((vfstab = setmntent(FSTAB, "r")) != 0) 
    {
	while ( mnt = getmntent(vfstab))
	{
	  if ( !strcmp(mnt->mnt_dir,"/") )	
	    if (stat(mnt->mnt_fsname, &stbr) == 0)
		if (stbr.st_rdev == stbd.st_rdev)
		    is_root = 1;

	  if ( !strcmp(mnt->mnt_dir,"/usr") )
	    if (stat(mnt->mnt_fsname, &stbr) == 0)
		if (stbr.st_rdev == stbd.st_rdev)
		    is_usr = 1;
	}
	endmntent(vfstab);
    }

    /* XXX - only works if filename is a block device or if
       character and block device has the same dev_t value */
    if (is_root == 0 && is_usr == 0 && ustat(stbd.st_rdev, &usb) == 0) {
	fprintf(stderr, "hfs fsck: sanity check: %s already mounted\n", filename);
	exit(33);
    }
    /*
     * We mount the ufs root file system read-only first.  After fsck
     * runs, we remount the root as read-write.  Therefore, we no longer
     * check for different values for fs_state between the root file 
     * system and the rest of file systems.
     */
    if (! ((sblock.fs_clean ==FS_CLEAN || sblock.fs_clean ==FS_OK))) {
	fprintf(stderr, "hfs fsck: sanity check: %s needs checking\n", filename);
	exit(32);
    }
    fprintf(stderr, "hfs fsck: sanity check: %s okay\n", filename);
    exit(0);
}
#endif
/* see if all numbers */
numbers(yp)
	char	*yp;
{
	if (yp == NULL)
		return (0);
	while ('0' <= *yp && *yp <= '9')
		yp++;
	if (*yp)
		return (0);
	return (1);
}
#endif

/* Convert a raw device name into a block device name. 
 * If the block device is not found, return the raw device name.
 * For HP and SUN, the returned value is not changed. For other 
 * platforms it is changed (I see no rhyme or reason -jpm).
 */
char *
unrawname(rawdev)
  char *rawdev;
{
  static char bldev[256];
  struct stat statbuf;
  int code, i;

  code = stat(rawdev, &statbuf);
  if ((code < 0) || !S_ISCHR(statbuf.st_mode))
     return(rawdev);                           /* Not a char device */

  for (i=strlen(rawdev)-2; i>=0; i--) {
     if ((rawdev[i] == '/') && (rawdev[i+1] == 'r')) {
        strcpy(bldev, rawdev);
        bldev[i+1] = 0;
        strcat(bldev, &rawdev[i+2]);

        code = stat(bldev, &statbuf);          /* test for block device */
        if (!code && S_ISBLK(statbuf.st_mode)) {
#if defined(AFS_HPUX_ENV) || defined(AFS_SUN5_ENV)
           return(bldev);
#else
	   strcpy(rawdev, bldev);              /* Replace */
	   return(rawdev);
#endif
	}
     }
  }
  return(rawdev);
}

/* Convert a block device name into a raw device name. 
 * If the block device is not found, return null
 */
char *
rawname(bldev)
  char *bldev;
{
  static char rawdev[256];
  struct stat statbuf;
  int code, i;

  for (i=strlen(bldev)-1; i>=0; i--) {
     if (bldev[i] == '/') {
        strcpy(rawdev, bldev);
        rawdev[i+1] = 'r';
        rawdev[i+2] = 0;
        strcat(rawdev, &bldev[i+1]);

        code = stat(rawdev, &statbuf);
        if (!code && S_ISCHR(statbuf.st_mode))
           return(rawdev);
     }
  }
  return (char *)0;
}
