/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

/*
 * Copyright (c) 1980 Regents of the University of California.
 * All rights reserved.  The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 */

#ifndef lint
char copyright[] =
"@(#) Copyright (c) 1980 Regents of the University of California.\n\
 All rights reserved.\n";
#endif /*not lint*/

/*
 * #ifndef lint
 * static char sccsid[] = "@(#)main.c	5.8 (Berkeley) 5/3/88";
 * #endif not lint
 */

#ident "$Header: main.c 1.8 89/08/07 $"

/* $Log:	main.c,v $
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/ufsinode.h>
#include <sys/ufsfilsys.h>
#include <sys/uadmin.h>
#include <sys/boot.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <mntent.h>
#include <fcntl.h>
#define INKERNEL
#include <sys/ufsdir.h>
#undef INKERNEL
#include "fsck.h"

char	*rawname();
void	catch();
void	catchquit();
int	returntosingle;
char	*devname;
extern char *malloc();
extern void sync();
extern void free();
unsigned sleep();

char	*checklist = "/etc/checklist";

main(argc, argv)
	int	argc;
	char	**argv;
{
	int	sumstatus;
	int	fromfile;		/* read file to get file systems? */

	sync();
	while (--argc > 0 && **++argv == '-') {
		switch (*++*argv) {

		case 'p':	/* preen */
		case 'q':	/* less verbose */
			preen++;
			break;

		case 'A':
			if (argv[0][1] != '\0') {
				sflag = atoi(argv[0]+1);
			} else {
				sflag = atoi(*++argv);
				argc--;
			}
			printf("Alternate super block location: %d\n", sflag);
			break;

		case 'c':
			cvtflag++;
			break;

		case 'd':
			debug++;
			break;

		case 'b':
			bflag++;
			break;
		case 'n':	/* default no answer flag */
		case 'N':
			nflag++;
			yflag = 0;
			break;

		case 'y':	/* default yes answer flag */
		case 'Y':
			yflag++;
			nflag = 0;
			break;

		/* give warning for unsupported System V flags */
		case 't':
		case 'T':
		case 's':	/* salvage flag */
		case 'S':	/* conditional salvage */
		case 'D':
		case 'f':
			fprintf(stderr, "fsck: WARNING: -%c not supported\n",
				**argv);
			break;

		default:
			errexit("%c option?\n", **argv);
		}
	}
	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
		(void)signal(SIGINT, catch);
	if (preen)
		(void)signal(SIGQUIT, catchquit);
	/* if no args, get file systems from checklist file */
	fromfile = argc == 0;
	if (fromfile)
		argc = new_args(&argv);

	hotroot = 0;
	sumstatus = 0;

	switch (argc) {
	case 1:
		if (fromfile)
			sumstatus = checkfilesys(rawname(*argv));
		else
			sumstatus = checkfilesys(*argv);
		break;
	default:
		if (preen)
			preen_list(argc, argv, checkfilesys);
		/* exit from preen_list */

		while (argc-- > 0) {
			hotroot = 0;
			if (fromfile)
				sumstatus |= checkfilesys(rawname(*argv++));
			else
				sumstatus |= checkfilesys(*argv++);
		}
		break;
	case 0:
		/* no file systems */
		break;
	}

	return(sumstatus);
}

/*
 * The /etc/checklist file has only one (block) file system per line,
 * no comments and no extra fields.
 *
 * Read in the whole file and create a new 'argv'.  Convert block device
 * name into raw device name.  Return new 'argc'.
 */

new_args(argv)
	char ***argv;
{
	int	fd;			/* checklist file descriptor */
	char	*checkbuf;		/* checklist file buffer */
	register char	*cbp;		/* checkbuf pointer */
	register int	csize;		/* checklist size */
	struct stat	statbuf;	/* checklist status info */
	int	lines;			/* number of lines in checklist */
	int	nargc;			/* new argc used as return value */
	char	**nargv;		/* new argv[] */
	int	i;			/* loop counter */

	if ((fd = open(checklist, O_RDONLY)) < 0
	|| fstat(fd, &statbuf) < 0)
		errexit("Can't open checklist file: %s\n", checklist);

	/* put the size in a register.  exit if nothing to read */
	if ((csize = statbuf.st_size) == 0)
		return(0);

	/* get enough memory to hold entire file plus NULL character */
	if ((checkbuf = (char *) malloc((unsigned)csize+1)) == NULL
	|| read(fd, checkbuf, (unsigned)csize) != csize)
		errexit("Can't read checklist file: %s\n", checklist);

	(void)close (fd);

	/* NULL terminate the buffer and count lines in the file */
	lines = 0;
	checkbuf[csize] = '\0';
	for (cbp = checkbuf; *cbp; cbp++)
		if (*cbp == '\n')
			lines++;
	if (*(cbp-1) != '\n')		/* no newline for last line */
		lines++;
	lines++;			/* one extra for NULL nargv */
	
	if ((nargv = (char **) malloc(lines*sizeof(char *))) == NULL)
		errexit("Out of memory.\n");

	nargc = 0;
	nargv[nargc] = strtok(checkbuf, "\n \t");
	while (nargc < lines-1 && nargv[nargc])
		nargv[++nargc] = strtok(NULL, "\n \t");

	/*
	 * get enough memory to hold entire file plus one extra
	 * character ('r') for each line.
	 */
	if ((cbp = (char *) malloc((unsigned)(csize+lines))) == NULL)
		errexit("Out of memory.\n");
	for (i = 0; i < nargc; i++) {
		nargv[i] = strcpy(cbp, rawname(nargv[i]));
		cbp += strlen(cbp) + 1;
	}

	*argv = nargv;
	return(nargc);
}

#define setstate(fsp, state)	if ((fsp)->fs_postblformat == FS_42POSTBLFMT) \
					(fsp)->fs_un.fs_dsp.dfs_state = (state); \
				else \
					(fsp)->fs_state = (state);
checkfilesys(filesys)
	char *filesys;
{
	daddr_t n_ffree, n_bfree;
	struct dups *dp;
	struct zlncnt *zlnp;
	char rebflg;		/* needs reboot if set */
	char fixstate;		/* is FS_STATE to be fixed? */

	devname = filesys;
	hotroot = 0;
	if (setup(filesys) == 0) {
		if (preen)
			pfatal("CAN'T CHECK FILE SYSTEM.");
		return(8);
	}
	/*
	 * 1: scan inodes tallying blocks used
	 */
	if (preen == 0) {
		printf("** Last Mounted on %s\n", sblock.fs_fsmnt);
		if (hotroot)
			printf("** Root file system\n");
		printf("** 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");
		printf("** Phase 1b - Rescan For More DUPS\n");
		pass1b();
	}

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

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

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

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

	/*
	 * print out summary statistics
	 */
	n_ffree = sblock.fs_cstotal.cs_nffree;
	n_bfree = sblock.fs_cstotal.cs_nbfree;
	pwarn("%d files, %d used, %d free ",
	    n_files, n_blks, n_ffree + sblock.fs_frag * n_bfree);
	printf("(%d frags, %d blocks, %.1f%% fragmentation)\n",
	    n_ffree, n_bfree, (float)(n_ffree * 100) / sblock.fs_dsize);
	if (debug && (n_files -= imax - ROOTINO - sblock.fs_cstotal.cs_nifree))
		printf("%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 -= fmax - (n_ffree + sblock.fs_frag * n_bfree))
			printf("%d blocks missing\n", n_blks);
		if (duplist != NULL) {
			printf("The following duplicate blocks remain:");
			for (dp = duplist; dp; dp = dp->next)
				printf(" %d,", dp->dup);
			printf("\n");
		}
		if (zlnhead != NULL) {
			printf("The following zero link count inodes remain:");
			for (zlnp = zlnhead; zlnp; zlnp = zlnp->next)
				printf(" %d,", zlnp->zlncnt);
			printf("\n");
		}
	}
	zlnhead = (struct zlncnt *)0;
	duplist = (struct dups *)0;
	ckflush();
	if (hotroot && (fsstate(&sblock) == FS_ACTIVE))
		fixstate = 1;
	else if ((fsstate(&sblock) + (long)sblock.fs_time) != FS_OKAY
	     || dfile.mod) {
		if (preen) {
			fixstate = 1;
		} else if (dfile.mod || rplyflag) {
			if (reply("SET FILE SYSTEM STATE TO OKAY") == 1)
				fixstate = 1;
			else
				fixstate = 0;
		} else if (nflag) {
			printf("%s FILE SYSTEM STATE NOT SET TO OKAY\n",devname);
			fixstate = 0;
		} else {
			printf("%s FILE SYSTEM STATE SET TO OKAY\n",devname);
			fixstate = 1;
		}
	}
	rebflg = dfile.mod;
	if (fixstate) {
		(void)time(&sblock.fs_time);
		if (hotroot) {
			if (bflag || rebflg) {
				setstate(&sblock,
					FS_OKAY - (long)sblock.fs_time);
			 } else {
				setstate(&sblock, FS_ACTIVE);
			}
		} else {
			setstate(&sblock, FS_OKAY - (long)sblock.fs_time);
		}
		sbdirty();
	}

	ckfini();
	free(blockmap);
	free(statemap);
	free((char *)lncntp);
	if (!rebflg)
		return(0);
	if (hotroot) {
		printf("\n%s ***** ROOT FILE SYSTEM WAS MODIFIED *****\n",
			devname);
		if (bflag) {
			printf("  ***** SYSTEM RETURNING TO FIRMWARE *****\n");
			printf("  ***** will REBOOT if AUTO enabled *****\n");
			(void)fflush(stdout);
			(void)sleep(2);	/* for print and sync to finish */
			(void)offline_all();
			uadmin(A_REBOOT, AD_BOOT_NOKILL, RB_NOSYNC);
		}
		printf("%s ***** BOOT UNIX (NO SYNC!) *****\n",devname);
		for(;;);
	}
	printf("%s ***** FILE SYSTEM WAS MODIFIED *****\n",devname);
	return(0);
}

/*
 * Convert a block device name to the equivalent raw name, by prepending
 * an "r" after "/dev/".  The new name is stuck into static storage.
 * If "/dev/" is not present - just return the name as is.
 *
 * Note that this routine will (correctly) not convert "/dev/root".
 * Examples of conversion might be:
 *	/dev/zd0a	yields	/dev/rzd0a
 *	/dev/dsk/zd0s0	yields	/dev/rdsk/zd0s0
 */

char *
rawname(name)
	register char	*name; 
{
	register char	*p;
	static char	newnm[MAXPATHLEN];	/* don't trash 'name' */

	if (strncmp(name, "/dev/", 5)) {
		return(name);
	}
	p = name + 5;
	if (*p == 'r')
		return(name);
	sprintf(newnm, "/dev/r%s", p);
	return(newnm);
}
