/*
 * e2fsck.c - a consistency checker for the new extended file system.
 *
 * Copyright (C) 1992, 1993 Remy Card (card@masi.ibp.fr)
 *
 * Copyright (C) 1991 Linus Torvalds. This file may be redistributed as per
 * the GNU copyright.
 */

/* Usage: e2fsck [-dfpnsvy] device
 *	-d -- debugging this program
 *	-f -- checking the fs even if it is marked valid
 *	-p -- "preen" the filesystem
 * 	-n -- open the filesystem r/o mode; never try to fix problems
 *	-s -- super-block info
 *	-v -- verbose (tells how many files)
 * 	-y -- always answer yes to questions
 *
 * The device may be a block device or a image of one, but this isn't
 * enforced (but it's not much fun on a character device :-). 
 */

#define E2FSCK_VERSION	"0.0"
#define E2FSCK_EDIT_DATE "11-Oct-93"

#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <time.h>
#include <getopt.h>
#include <unistd.h>
#include <mntent.h>
#include <sys/ioctl.h>
#include <malloc.h>

#include "et/com_err.h"
#include "e2fsck.h"

#if 0
#define TEST_BUFFER_BLOCKS	 16

#define UPPER(size,n)		((size + ((n) - 1)) / (n))
#define INODE_SIZE		(sizeof (struct ext2_inode))
#define INODE_BLOCKS		UPPER(INODES, EXT2_INODES_PER_BLOCK (Super))
#define INODE_BUFFER_SIZE	(INODE_BLOCKS * EXT2_BLOCK_SIZE (Super))

#endif

extern int isatty(int);

char * program_name = "e2fsck";
char * device_name = NULL;
ext2_filsys	fs;

/* Command line options */
int nflag = 0;
int yflag = 0;
int tflag = 0;			/* Do timing */
int preen = 0;
int rwflag = 1;
int inode_buffer_blocks = 0;

int verbose = 0;
int list = 0;
int show = 0;
int debug = 0;
int force = 0;

struct resource_track	global_rtrack;

static int root_filesystem = 0;
static int read_only_root = 0;

static void usage()
{
	fprintf(stderr, "Usage: %s [-panyrdfsvtF] /dev/name\n", program_name);
	exit(FSCK_USAGE);
}

static void show_stats()
{
	int inodes, inodes_used, blocks, blocks_used;
	int dir_links;
	int num_files, num_links;

	dir_links = 2 * fs_directory_count - 1;
	num_files = fs_total_count - dir_links;
	num_links = fs_links_count - dir_links;
	inodes = fs->super->s_inodes_count;
	inodes_used = (fs->super->s_inodes_count -
		       fs->super->s_free_inodes_count);
	blocks = fs->super->s_blocks_count;
	blocks_used = (fs->super->s_blocks_count -
		       fs->super->s_free_blocks_count);
	
	if (!verbose) {
		printf("%s: %d/%d files, %d/%d blocks\n", device_name,
		       inodes_used, inodes, blocks_used, blocks);
		return;
	}
	printf ("\n%6d inode%s used (%d%%)\n", inodes_used,
		(inodes_used != 1) ? "s" : "",
		100 * inodes_used / inodes);
	printf ("%6d block%s used (%d%%)\n"
		"%6d bad block%s\n", blocks_used,
		(blocks_used != 1) ? "s" : "",
		100 * blocks_used / blocks, fs_badblocks_count,
		fs_badblocks_count != 1 ? "s" : "");
	printf ("\n%6d regular file%s\n"
		"%6d director%s\n"
		"%6d character device file%s\n"
		"%6d block device file%s\n"
		"%6d fifo%s\n"
		"%6d link%s\n"
		"%6d symbolic link%s (%d fast symbolic link%s)\n"
		"%6d socket%s\n"
		"------\n"
		"%6d file%s\n",
		fs_regular_count, (fs_regular_count != 1) ? "s" : "",
		fs_directory_count, (fs_directory_count != 1) ? "ies" : "y",
		fs_chardev_count, (fs_chardev_count != 1) ? "s" : "",
		fs_blockdev_count, (fs_blockdev_count != 1) ? "s" : "",
		fs_fifo_count, (fs_fifo_count != 1) ? "s" : "",
		fs_links_count - dir_links,
		((fs_links_count - dir_links) != 1) ? "s" : "",
		fs_symlinks_count, (fs_symlinks_count != 1) ? "s" : "",
		fs_fast_symlinks_count, (fs_fast_symlinks_count != 1) ? "s" : "",
		fs_sockets_count, (fs_sockets_count != 1) ? "s" : "",
		fs_total_count - dir_links,
		((fs_total_count - dir_links) != 1) ? "s" : "");
}

static void check_mount()
{
	FILE * f;
	struct mntent * mnt;
	int cont;
	int fd;

	/*
	 * Check to see if the root was mounted read-only.
	 */
	fd = open(MOUNTED, O_RDWR);
	if (fd < 0) {
		if (errno == EROFS) {
			read_only_root = 1;
			return;
		}
	} else
		close(fd);
	
	if ((f = setmntent (MOUNTED, "r")) == NULL)
		return;
	while ((mnt = getmntent (f)) != NULL)
		if (strcmp (device_name, mnt->mnt_fsname) == 0)
			break;
	endmntent (f);
	if (!mnt)
		return;

	if  (!strcmp(mnt->mnt_dir, "/"))
		root_filesystem = 1;

	/*
	 * If the root is mounted read-only, then /etc/mtab is
	 * probably not correct; so we won't issue a warning based on
	 * it.
	 */
	if (read_only_root)
		return;

	if (!rwflag) {
		printf("Warning!  %s is mounted.\n", device_name);
		return;
	}

	printf ("%s is mounted.  ", device_name);
	if (isatty (0) && isatty (1))
		cont = ask_yn("Do you really want to continue", -1);
	else
		cont = 0;
	if (!cont) {
		printf ("check aborted.\n");
		exit (0);
	}
	return;
}

static void sync_disks()
{
	int	i;
	
	for (i = 0 ; i < 3; i++)
		sync();
}

static void PRS(int argc, char *argv[])
{
	int flush = 0;
	char c;
#ifdef MTRACE
	extern void *mallwatch;
#endif

	setbuf(stdout, NULL);
	setbuf(stderr, NULL);
	initialize_ext2_error_table();
	
	fprintf (stderr, "NEW e2fsck %s, %s for EXT2 FS %s\n",
		 E2FSCK_VERSION, E2FSCK_EDIT_DATE, EXT2FS_VERSION);
	if (argc && *argv)
		program_name = *argv;
	while ((c = getopt (argc, argv, "panyrB:dfsvtFVM:")) != EOF)
		switch (c) {
		case 'p':
		case 'a':
			preen = 1;
			rwflag = 1;
			break;
		case 'n':
			nflag = 1;
			rwflag = 0;
			break;
		case 'y':
			yflag = 1;
			rwflag = 1;
			break;
		case 't':
			tflag++;
			break;
		case 'r':
			/* What we do by default, anyway! */
			break;
			
		case 'B':
			inode_buffer_blocks = atoi(optarg);
			break;
		case 'd':
			debug = 1;
			break;
		case 'f':
			force = 1;
			break;
		case 'F':
			flush = 1;
			break;
		case 's':
			show = 1;
			break;
		case 'v':
			verbose = 1;
			break;
		case 'V':
			exit(0);
			break;
#ifdef MTRACE
		case 'M':
			mallwatch = (void *) strtol(optarg, NULL, 0);
			break;
#endif
		default:
			usage ();
		}
	if (optind != argc - 1)
		usage ();
	device_name = argv[optind];
	if (flush) {
		int	fd = open(device_name, O_RDONLY, 0);

		if (fd < 0) {
			com_err("open", errno, "while opening %s for flushing",
				device_name);
			exit(FSCK_ERROR);
		}
		if (ioctl(fd, BLKFLSBUF, 0) < 0) {
			com_err("BLKFLSBUF", errno, "while trying to flush %s",
				device_name);
			exit(FSCK_ERROR);
		}
		close(fd);
	}
}
					
int main (int argc, char *argv[])
{
	errcode_t	retval;
	int		exit_value = FSCK_OK;
	
#ifdef MTRACE
	mtrace();
#endif
#ifdef MCHECK
	mcheck(0);
#endif
	
	init_resource_track(&global_rtrack);

	PRS(argc, argv);

	check_mount();
	
	if (!preen && !nflag && !yflag) {
		if (!isatty (0) || !isatty (1))
			die ("need terminal for interactive repairs");
	}
	sync_disks();
	retval = ext2fs_open(device_name, rwflag ? EXT2_FLAG_RW : 0, 0, 0,
			     unix_io_manager, &fs);
	if (retval) {
		com_err(program_name, retval, "while trying to open %s",
			device_name);
		fatal_error(0);
	}
	if (fs->super->s_valid && !force) {
		printf("%s is clean, no check.\n", device_name);
		exit(FSCK_OK);
	}
	
	/*
	 * Mark the system as valid, 'til proven otherwise
	 */
	ext2fs_mark_valid(fs);
	
	pass1(fs);
	pass2(fs);
	pass3(fs);
	pass4(fs);
	pass5(fs);

#ifdef MTRACE
	mtrace_print("Cleanup");
#endif
	if (ext2fs_test_changed(fs)) {
		exit_value = FSCK_NONDESTRUCT;
		if (!preen)
			printf("\n%s: ***** FILE SYSTEM WAS MODIFIED *****\n",
			       device_name);
		if (root_filesystem && !read_only_root) {
			printf("%s: ***** REBOOT LINUX *****\n", device_name);
			exit_value = FSCK_DESTRUCT;
		}
	}
	if (!ext2fs_test_valid(fs))
		exit_value = FSCK_UNCORRECTED;
	if (rwflag) {
		if (ext2fs_test_valid(fs) && !fs->super->s_valid) {
			fs->super->s_valid = 1;
			ext2fs_mark_super_dirty(fs);
		} else if (!ext2fs_test_valid(fs) && fs->super->s_valid) {
			fs->super->s_valid = 0;
			ext2fs_mark_super_dirty(fs);
		}
	}
	show_stats();

	if (ext2fs_test_bb_dirty(fs)) {
		retval = ext2fs_write_block_bitmap(fs);
		if (retval) {
			com_err(program_name, retval,
				"while retrying to write block bitmaps for %s",
				device_name);
			fatal_error(0);
		}
	}

	if (ext2fs_test_ib_dirty(fs)) {
		retval = ext2fs_write_inode_bitmap(fs);
		if (retval) {
			com_err(program_name, retval,
				"while retrying to write inode bitmaps for %s",
				device_name);
			fatal_error(0);
		}
	}
	ext2fs_close(fs);
	sync_disks();
	
	if (tflag)
		print_resource_track(&global_rtrack);
	
	return exit_value;
}
