/* -*- Mode: Linux-C -*-
 *
 * lfs/mkfs.lfs.c
 * Copyright (c) 1997 Christopher R. Waterson
 * All rights reserved.
 *
 * To Do
 *
 * . Implement the `-c' option (check for bad blocks).
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/fs.h>
#include <linux/lfs_fs.h>
#include <linux/stat.h>


/*
 * The size of the segments in blocks
 */
unsigned short segment_size = 1024;

/*
 * The name of the device on which we'll create the filesystem
 */
char* device_name = NULL;

/*
 * Set to `1' at the command line to print lots of info
 */
int verbose = 0;

/*
 * Should we check for bad blocks before we create the filesystem?
 */
int check_for_bad_blocks = 0;

/*
 * The number of segments in the filesystem
 */
int nsegments = 0;

/*
 * Should the smap be in a fixed location (debugging only)?
 */
int fixed_smap = 0;


/*
 * Print out how to use this thing
 */
static void
usage(char* bin)
{
	fprintf(stderr, "%s: [-c] [-f] [-n nsegments] [-s nblocks] [-v] special\n", bin);
	fprintf(stderr, "  -c: check for bad blocks\n");
	fprintf(stderr, "  -f: fixed smap\n");
	fprintf(stderr, "  -n nsegments: create specified number of segments (default is to\n");
	fprintf(stderr, "       fill the device)\n");
	fprintf(stderr, "  -s nblocks: set segment size to nblocks 1K blocks (default=%d)\n",
		segment_size);
	fprintf(stderr, "  -v: verbose\n");
	fprintf(stderr, "The `special' parameter is the device on which you wish to create\n");
	fprintf(stderr, "the filesystem.\n");
}

/*
 * Parse the command line
 */
static void
parse_args(int argc, char* argv[])
{
	char c;

	if (argc < 2) {
	    /* Gotta have a device name! */
	    usage(argv[0]);
	    exit(1);
	}

	while ((c = getopt(argc, argv, "cfn:s:v")) != -1) {
		switch (c) {
		case 'c':
			check_for_bad_blocks = 1;
			break;

		case 'f':
			fixed_smap = 1;
			break;

		case 'n':
			nsegments = atoi(optarg);
			break;

		case 's':
			segment_size = atoi(optarg);
			break;

		case 'v':
			verbose = 1;
			break;

		default:
			usage(argv[0]);
			exit(1);
		}
	}

	device_name = argv[argc - 1];
}


/*
 * Do an ioctl on the device to get the device size. From there,
 * compute the number of segments in the filesystem.
 */
static int
get_nsegments(int fd)
{
	int sz = 0; /* The size of the device, in 512 byte blocks */
	int nblocks;

	if (ioctl(fd, BLKGETSIZE, &sz) < 0) {
		perror("unable to determine device size");
		exit(1);
	}

	nblocks = (sz * 512) / BLOCK_SIZE;
	nblocks -= 1; /* The superblock */

	return nblocks / segment_size;
}

char raw_block[BLOCK_SIZE];

struct lfs_inode root;
struct lfs_inode smap;
struct lfs_inode imap;
struct lfs_super_block sb;

unsigned long current_block = 0;
unsigned long segment_summary_block;
unsigned long root_inode_block;
unsigned short nsmapblocks;	/* The number of blocks in the smap */

int fd;



/*
 * Block 0: the superblock -- empty at first
 */
void
write_empty_superblock()
{
	memset(raw_block, 0, sizeof(raw_block));
	write(fd, raw_block, sizeof(raw_block));
	current_block++;
}

/*
 * The root directory block
 */
void
write_root_directory_block()
{
	struct lfs_dir_entry *dentry = (struct lfs_dir_entry *) raw_block;
	memset(raw_block, 0, sizeof(raw_block));

	dentry[0].e_ino      = LFS_ROOT_INO;
	dentry[0].e_name_len = 1;
	strcpy(dentry[0].e_name, ".");

	dentry[1].e_ino	     = LFS_IMAP_INO;
	dentry[1].e_name_len = 4;
	strcpy(dentry[1].e_name, "imap");

	if (! fixed_smap) {
		dentry[2].e_ino      = LFS_SMAP_INO;
		dentry[2].e_name_len = 4;
		strcpy(dentry[2].e_name, "smap");
	}

	write(fd, raw_block, sizeof(raw_block));

	sb.s_segment_one = root.i_zone[0] = current_block++;
}

/*
 * The root i-node
 */
void
write_root_inode()
{
	root.i_ino	= LFS_ROOT_INO;
	root.i_mode	= S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO;
	root.i_uid	= 0; /* root */
	root.i_gid	= 0; /* root */
	root.i_nlink	= 2;
	root.i_size	= BLOCK_SIZE;
	root.i_blocks	= 1;
	root.i_ctime	= time(NULL);
	root.i_mtime	= time(NULL);

	memset(raw_block, 0, sizeof(raw_block));
	memcpy(raw_block, &root, sizeof(root));
	write(fd, raw_block, sizeof(raw_block));
	root_inode_block = current_block++;
}


/*
 * The initial smap blocks, which are just blasted out to disk
 * zero-filled so we can set up the smap inode.
 */
void write_empty_smap_blocks()
{
	char iblock_buffer[BLOCK_SIZE];
	unsigned long *iblock = (unsigned long *) iblock_buffer;
	int i;

	memset(raw_block, 0, sizeof(raw_block));
	memset(iblock_buffer, 0, sizeof(iblock_buffer));

	for (i = 0; i < nsmapblocks; ++i) {
		write(fd, raw_block, sizeof(raw_block));

		if (i < LFS_NDBLOCK) {
			smap.i_zone[i] = current_block;
		} else if (i < LFS_NDBLOCK + LFS_NIBLOCK) {
			iblock[i - LFS_NDBLOCK] = current_block;
		} else {
			/* Too big! We can only handle a single
                           indirect block right now... */
			fprintf(stderr, "disk too big! smap overflow\n");
			exit(1);
		}

		++current_block;
	}

	if (fixed_smap)
		/* The smap is fixed on the dist starting at block
                   one...no need to write any extra inode info. */
		return;

	/* Now write the iblock */
	if (nsmapblocks > LFS_NDBLOCK) {
		write(fd, iblock_buffer, sizeof(iblock_buffer));
		smap.i_zone[LFS_NDBLOCK] = current_block++;
	}
}

/*
 * The smap inode.
 */
void write_smap_inode()
{
	smap.i_ino	= LFS_SMAP_INO;
	smap.i_mode	= S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
	smap.i_uid	= 0 /* root */;
	smap.i_gid	= 0 /* root */;
	smap.i_nlink	= 1;
	smap.i_size	= nsegments * sizeof(struct lfs_smap_entry);
	smap.i_blocks	= nsmapblocks;
	smap.i_ctime	= time(NULL);
	smap.i_mtime	= time(NULL);

	memset(raw_block, 0, sizeof(raw_block));
	memcpy(raw_block, &smap, sizeof(smap));

	write(fd, raw_block, sizeof(raw_block));
	sb.s_segment_map = current_block++;
}

/*
 * The initial imap block
 */
void
write_initial_imap_block()
{
	struct lfs_imap_entry *ime = (struct lfs_imap_entry *) raw_block;
	memset(raw_block, 0, sizeof(raw_block));

	ime[0].i_blocknr = root_inode_block;	/* The root inode */
	ime[0].i_atime   = time(NULL);

	write(fd, raw_block, sizeof(raw_block));
	imap.i_zone[0] = current_block++;
}



/*
 * The imap inode
 */
void
write_imap_inode()
{
	imap.i_ino	= LFS_IMAP_INO;
	imap.i_mode	= S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
	imap.i_uid	= 0 /* root */;
	imap.i_gid	= 0 /* root */;
	imap.i_nlink	= 1;
	imap.i_size	= BLOCK_SIZE;
	imap.i_blocks	= 1;
	imap.i_ctime	= time(NULL);
	imap.i_mtime	= time(NULL);

	memset(raw_block, 0, sizeof(raw_block));
	memcpy(raw_block, &imap, sizeof(imap));
	write(fd, raw_block, sizeof(raw_block));
	sb.s_inode_map = current_block++;
}

/*
 * The segment summary
 */
void write_segment_summary()
{
	int entry = 0;
	ino_t *ssmry = (unsigned long *) raw_block;
	memset(raw_block, 0, sizeof(raw_block));

	ssmry[entry++] = LFS_ROOT_INO;

	if (! fixed_smap)
		ssmry[entry++] = LFS_SMAP_INO;

	ssmry[entry++] = LFS_IMAP_INO;

	ssmry[entry++] = 0;

	write(fd, raw_block, sizeof(raw_block));
	segment_summary_block = current_block++;

	/* This doesn't count as a "used block" for the
	   segment summary */
}


/*
 * Now back to write the superblock
 */
void
write_superblock()
{
	sb.s_magic		= LFS_SUPER_MAGIC;
	sb.s_segments		= nsegments;
	sb.s_free_segments	= nsegments - 1;
	sb.s_segment_size	= segment_size;
	sb.s_flags	       |= fixed_smap ? LFS_FIXED_SMAP : 0;

	/* The first one is allocated with the root inode and
	   all that other crap we just wrote */
	sb.s_next_free_segment	= 2;

	/* Two for the imap and smap inodes */
	sb.s_inodes		= LFS_IME_PER_BLOCK + 2;

	memset(raw_block, 0, sizeof(raw_block));
	memcpy(raw_block, &sb, sizeof(sb));

	lseek(fd, 0, SEEK_SET);
	write(fd, raw_block, sizeof(raw_block));
}


/*
 * ...and back to write the segment map
 */
void
write_smap_blocks_with_data()
{
	int i;
	struct lfs_smap_entry *sme = (struct lfs_smap_entry *) raw_block;
	memset(raw_block, 0, sizeof(raw_block));
	
	sme[0].e_mtime		= time(NULL);
	sme[0].e_used_blocks	= segment_summary_block - sb.s_segment_one;

	/* This is relative to the segment's start */
	sme[0].e_summary_block	= segment_summary_block - sb.s_segment_one;

	sme[0].e_prev		= 0;
	sme[0].e_next		= 0;

	/* Since we know that the smap blocks are contiguous
	   (because we wrote them that way, above), just seek
	   to the first one and off we go... */
	{
		unsigned long smap_block_0 =
			fixed_smap ? 1 : smap.i_zone[0];

		if (verbose) 
			printf("returning to block %ld to write smap...\n", smap_block_0);

		lseek(fd, smap_block_0 * BLOCK_SIZE, SEEK_SET);
	}

	for (i = 1; i < nsegments; ++i) {
		int entry = i % LFS_SME_PER_BLOCK;

		if (entry == 0) {
				/* Okay, we just wrote the last entry
                                   in the previous block, so flush
                                   stuff to disk */
			write(fd, raw_block, sizeof(raw_block));
			memset(raw_block, 0, sizeof(raw_block));
		}

		sme[entry].e_mtime		= 0;
		sme[entry].e_used_blocks	= 0;
		sme[entry].e_summary_block	= 0;
		sme[entry].e_prev		= i;
		sme[entry].e_next		= i + 2;

		/* Fix the links for the first and last free segment --
		   remember that everything here is 1-indexed! */
		if (i == 1) {
			sme[entry].e_prev = nsegments;
		} else if (i == nsegments - 1) {
			sme[entry].e_next = 2;
		}
	}
		
	/* Now, write the very last smap block */
	write(fd, raw_block, sizeof(raw_block));
}



int
main(int argc, char* argv[])
{
	parse_args(argc, argv);

	if ((fd = open(device_name, O_RDWR)) < 0) {
		perror("open failed");
		exit(1);
	}

	if (nsegments <= 0) {
		nsegments = get_nsegments(fd);
	} else {
		/* Check the specified parameter vs. the user-supplied
                   paramter. Print a warning message if things seem
                   funky. */
		int knsegments = get_nsegments(fd);
		if (nsegments > knsegments) {
			fprintf(stderr, "warning: this device only seems "
				"capable of handling %d segments.\n",
				knsegments);
		}
	}

	if (verbose) {
		printf("fs will contain %d segments, each with %d %d-byte "
		       "blocks.\n", nsegments, segment_size, BLOCK_SIZE);
	}

	nsmapblocks = ((nsegments - 1) / LFS_SME_PER_BLOCK) + 1;

	if (verbose) {
		printf("the smap will contain %d %d-byte blocks.\n",
		       nsmapblocks, BLOCK_SIZE);
	}

	if (nsmapblocks > LFS_NDBLOCK + LFS_NIBLOCK) {
		fprintf(stderr, "double-indirect blocks not supported for "
			" smap yet. Sorry!\n");

		exit(1);
	}

	memset(&root, 0, sizeof(root));
	memset(&imap, 0, sizeof(imap));
	memset(&smap, 0, sizeof(smap));

	/* Write out the blocks */
	write_empty_superblock();
	if (fixed_smap) {
		/* Write the smap _before_ the real log segments start */
		write_empty_smap_blocks();
	}

	write_root_directory_block();
	write_root_inode();

	if (! fixed_smap) {
		/* Write the smap _inside_ the log */
		write_empty_smap_blocks();
		write_smap_inode();
	}

	write_initial_imap_block();
	write_imap_inode();
	write_segment_summary();
	write_superblock();
	write_smap_blocks_with_data();

	/* That all! */

	close(fd);
	sync();

	if (verbose) printf("Done.\n");
	return 0;
}

