/*-
 * Copyright (c) 2001 Alexander Leidinger
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <paths.h>

#include <sys/types.h>
#include <sys/disklabel.h>
#include <sys/param.h>
#include <sys/uio.h>

#include <libdisk.h>
#include <ufs/ffs/fs.h>
#include <ufs/ufs/quota.h>
#include <ufs/ufs/inode.h>
#include <unistd.h>

enum state {state_stop, state_goto_superblock, state_superblock, state_dinode};

int
main(int argc, char **argv)
{
	int dev, retval, slice_num;
#if 0
	u_int64_t num = 0;
#endif
	u_int8_t *buffer;
	u_long bad_block, part_size, sect_size;
	struct disk *drive;
	struct chunk *act_chunk;
	long part_offset;
        char part_name[64];     /* 64: as in libdisk:disk.c */
	enum state state;

#if 0
	if (MINBSIZE < sizeof(struct disklabel) ||
		 MINBSIZE < sizeof(struct fs))
	{
		puts("Internal error: Buffer not large enough.");
		exit(1);
	}
#endif

	if (3 != argc)
	{
		puts("No arg1 (device name, not a path, e.g. ad0 or da0) or arg2 (bad block number) specified.");
		exit(1);
	}

	bad_block = strtoul(argv[2], NULL, 0);
	if ((ULONG_MAX == bad_block && ERANGE == errno) ||
		(0 == bad_block && EINVAL == errno))
	{
		perror("Converting bad block number");
		exit(1);
	}

	drive = Open_Disk(argv[1]);
	if (NULL == drive)
	{
		perror("Open device");
		exit(1);
	}

#ifdef DEBUG
	Debug_Disk(drive);
	printf("Sector size = %lu\n", drive->sector_size);
#endif

	/* which slice contains the bad block? */
	act_chunk = drive->chunks;
	slice_num = 0;
	while (NULL != act_chunk &&
		(act_chunk->end <= bad_block || whole == act_chunk->type))
	{
		if (NULL != act_chunk->next )
		{
			act_chunk = act_chunk->next;
			++slice_num;
		} else if (NULL != act_chunk->part)
		{
			act_chunk = act_chunk->part;
		} else {
			act_chunk = NULL;
		}
	}

	/* safety net if the bad block is out of range (typo from user) */
	if (NULL != act_chunk)
	{
		printf("Bad block (%ld) in slice %u (%s)\n",
			bad_block, slice_num, chunk_n[act_chunk->type]);

		/* find the FFS partition */
		if (freebsd == act_chunk->type)
		{
			act_chunk = act_chunk->part;
			while (NULL != act_chunk &&
				act_chunk->end <= bad_block)
			{
				act_chunk = act_chunk->next;
			}
		}

		/* the bad block may be not in a used partition */
		if (NULL != act_chunk)
		{
			printf("Bad block in %s\n", act_chunk->name);
			part_offset = act_chunk->offset;
			part_size   = act_chunk->size;
			sect_size   = act_chunk->disk->sector_size;

			strcpy(part_name, _PATH_DEV);
			strcat(part_name, act_chunk->name);
		} else {
			puts("Bad block not an used partition");
			part_size = 0;
		}
	}

	Free_Disk(drive);
	drive = NULL;


	if (0 == part_size)
	{
		/* nothink do to */
		exit(0);
	}

	/* Ok, we have a valid partition, now let's try to find the inode */

	buffer = malloc(SBSIZE);
	if (NULL == buffer)
	{
		perror("Allocating buffer");
		exit(1);
	}

	dev = open(part_name, O_RDONLY);
	if (dev == -1)
	{
		perror("Open device");
		free(buffer);
		exit(1);
	}

	state = state_goto_superblock;
	do {
#if 0
		if (num > ++num)
		{
			puts("Aborting... drive too large.");
			break;
		}
#endif
		switch(state)
		{
		    case state_goto_superblock:
			if (-1 == lseek(dev, SBOFF, SEEK_SET))
			{
				perror("Seeking to superblock");
				state = state_stop;
			} else {
				state = state_superblock;
			}
			break;

		    case state_superblock:
			if (((struct fs *)buffer)->fs_magic == FS_MAGIC)
			{
				struct fs *fs = (struct fs *)buffer;
				u_int8_t *tmp_buf;

				/* superblock */
				printf("iblkno: %d, bsize: %d, fsize: %d, sblock: %d\n",
					fs->fs_iblkno, fs->fs_bsize,
					fs->fs_fsize, fs->fs_sblkno);

				/* resize buffer to block size of FS */
				tmp_buf = malloc(fs->fs_bsize);
				if (NULL == tmp_buf)
				{
					perror("Allocating FS buffer");
					state = state_stop;
					break;
				} else {
					free(buffer);
					buffer = tmp_buf;
				}

				/* XXX: fixme */
				lseek(dev, fs->fs_iblkno * fs->fs_bsize,
					SEEK_CUR);

				state = state_dinode;
			} else {
				puts("No superblock found.");
#if 0
				state = state_stop;
#endif
			}
			break;

		    case state_dinode:
			{
			struct dinode *dinode = ((struct dinode *)buffer);

			/* XXX: fixme */
			printf("inumber: %d\n", dinode->di_inumber);
			}
			state = state_stop;
			break;

		    case state_stop: /* FALLTHROUGH */
		    default:
			/* we shouldn't be here! */
			puts("programming error!");
			close(dev);
			free(buffer);
			exit(1);
			break;
		}
	} while ((retval = read(dev, buffer, MINBSIZE)) == MINBSIZE &&
		state != state_stop);

	if (-1 == retval)
	{
		perror("Reading device");
	}

	close(dev);
	free(buffer);

	return 0;
}
