/*-
 * Copyright (c) 2001 Mitsuru IWASAKI
 * All rights reserved.
 *
 * NOTE: This utility was created based on FreeBSD fdisk(8) and 
 *	 lphdisk 0.4 (http://www.procyon.com/~pda/lphdisk/) for
 *	 experimental purpose.  This software may be rewritten using
 *	 FreeBSD libdisk(3) from scratch in future.
 *
 * lphdisk is released under the Artistic License
 * http://www.perl.com/language/misc/Artistic.html
 *
 *      $FreeBSD$
 */

#include <sys/disklabel.h>
#include <sys/dkio.h>
#include <sys/stat.h>
#include <ctype.h>
#include <fcntl.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MBRSIGOFF	510
#define MAX_SEC_SIZE	2048	/* maximum section size that is supported */
#define MIN_SEC_SIZE	512	/* the sector size to start sensing at */

int secsize = 0;		/* the sensed sector size */

const char *disk;
const char *disks[] = {
  "/dev/wd0d", "/dev/da0", "/dev/od0", 0
};

struct disklabel disklabel;	/* disk parameters */

struct mboot
{
	unsigned char padding[2]; /* force the longs to be long aligned */
  	unsigned char *bootinst;  /* boot code */
  	off_t bootinst_size;
	struct	mbr_partition parts[4];
};
struct mboot mboot = {{0}, NULL, 0};

#define BOOT_MAGIC 0xAA55

int dos_cyls;
int dos_heads;
int dos_sectors;
int dos_cylsecs;

int fd;

/*
 * The header, with sector and checksum values set to 0
 *                      __ __                            __ __ __ __
 *   54 69 6D 4F  00 00 00 00  00 00 00 00  02 00 00 00  00 00 00 00
 *                    Checksum                         Size in Sectors - 2
 */
 
unsigned char header[512] = {		/* we start with an empty header */
  0x54, 0x69, 0x6D, 0x4F,   0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00,   0x02, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00
};

static int
get_params(void)
{

	if (ioctl(fd, DIOCGDINFO, &disklabel) == -1) {
		warnx("can't get disk parameters on %s; supplying dummy ones", disk);
		dos_cyls = 1;
		dos_heads = 1;
		dos_sectors = 1;
	} else {
		dos_cyls = disklabel.d_ncylinders;
		dos_heads = disklabel.d_ntracks;
		dos_sectors = disklabel.d_nsectors;
	}

	dos_cylsecs = dos_heads * dos_sectors;
	return (dos_cyls * dos_heads * dos_sectors);
}

/* Getting device status */
static int
open_disk(void)
{
	struct	stat st;

	if (stat(disk, &st) == -1) {
		warnx("can't get file status of %s", disk);
		return (-1);
	}
	if (!(st.st_mode & S_IFCHR))
		warnx("device %s is not character special", disk);
	if ((fd = open(disk, O_RDONLY)) == -1) {
		if(errno == ENXIO)
			return (-2);
		warnx("can't open device %s", disk);
		return (-1);
	}
	if (get_params() == -1) {
		warnx("can't get disk parameters on %s", disk);
		return (-1);
	}
	return (fd);
}

static ssize_t
read_disk(off_t sector, void *buf)
{

	lseek(fd,(sector * 512), 0);
	if( secsize == 0 ) {
		for( secsize = MIN_SEC_SIZE; secsize <= MAX_SEC_SIZE; secsize *= 2 ) {
			/* try the read */
			int size = read(fd, buf, secsize);
			if( size == secsize )
				/* it worked so return */
				return (secsize);
		}
	} else {
		return (read(fd, buf, secsize));
	}
	
	/* we failed to read at any of the sizes */
	return (-1);
}

static int
read_s0()
{

	mboot.bootinst_size = secsize;
	if (mboot.bootinst != NULL)
		free(mboot.bootinst);
	if ((mboot.bootinst = malloc(mboot.bootinst_size)) == NULL) {
		warnx("unable to allocate buffer to read fdisk "
		      "partition table");
		return (-1);
	}
	if (read_disk(0, mboot.bootinst) == -1) {
		warnx("can't read fdisk partition table");
		return (-1);
	}
	if (*(uint16_t *)&mboot.bootinst[MBRSIGOFF] != BOOT_MAGIC) {
		warnx("invalid fdisk partition table found");
		/* So should we initialize things */
		return (-1);
	}
	memcpy(mboot.parts, &mboot.bootinst[MBR_PARTOFF], sizeof(mboot.parts));
	return (0);
}

/*
 * create header
 */
void
create_header(u_int32_t partsize)
{
	unsigned int	lowbyte = 0;		/* low byte of each byte pair */
	unsigned int	highbyte = 0;		/* high byte of each byte pair */
	unsigned int	word = 0;		/* byte pair for checksum addition */
	unsigned int	checksum = 0;		/* running checksum total */
	int		i = 0;			/* general counter variable */

	header[19] = (partsize >> 24) & 0xff;	/* first byte in sector size */
	header[18] = (partsize >> 16) & 0xff;	/* second byte in sector size */
	header[17] = (partsize >> 8) & 0xff;	/* third byte in sector size */
	header[16] = partsize & 0xff;		/* fourth (last) byte in sector size */

	for (i = 20; i < 512; i++) {
		header[i] = 0xff;		/* header filler with FFs */
	}

	for (i = 511; i > 7; i = i - 2) {	/* compute the checksum */
		lowbyte = header[i-1];		/* read low byte */
		highbyte = header[i];		/* read high byte */

		word = lowbyte + (0x100 * highbyte);	/* merge bytes into word */

		checksum = checksum + word;	/* add word to checksum */

	}

	checksum = ~checksum;			/* invert checksum */
	header[6] = checksum & 0xff;		/* significant high byte of checksum */
	header[7] = (checksum >> 8) & 0xff;	/* significant low byte of checksum */

#if 0
	for (i = 0; i < 32; i++) {
		printf(" %02x", header[i]);
	}
	printf("\n");
#endif
}

/*
 * format the partition
 */
int
do_format(u_int32_t partsize)
{
	FILE	*partfile;			/* declare FILE structure */
	int	 i = 0;				/* generic counter variable */
	unsigned char	fresh_sector[512];	/* a PHDISK.EXE "formatted" sector */

	if (!(partfile = fopen("phformat.dump", "w"))) {
		return(-1);
	}

	for (i = 0; i < 512; i++) {
		fresh_sector[i] = 0x50;
	}

	fwrite(header, 1, 512, partfile);	/* write string of magic_header */
	fwrite(header, 1, 512, partfile);	/* write string of magic_header */

	/* write partsize "blank" sectors */
	for (i = 0; i < partsize; i++) {
		fwrite(fresh_sector, 1, 512, partfile);
	}

	fclose(partfile);                     /* close partition */
}

int
main(int argc, char *argv[])
{
	int	 i;
	int	 rv = 0;
	int	 phpart = -1;
	struct	 mbr_partition *partp;
	u_int32_t	 phsize = 0;

	for (i = 0; disks[i]; i++) {
		disk = disks[i];
		rv = open_disk();
		if(rv != -2) break;
	}
	if (rv < 0) {
		err(1, "cannot open any disk");
	}

	/* (abu)use mboot.bootinst to probe for the sector size */
	if ((mboot.bootinst = malloc(MAX_SEC_SIZE)) == NULL) {
		err(1, "cannot allocate buffer to determine disk sector size");
	}

	read_disk(0, mboot.bootinst);
	free(mboot.bootinst);
	mboot.bootinst = NULL;

	read_s0();
	for (i = 0; i < NMBRPART; i++) {
		partp = ((struct mbr_partition *) &mboot.parts) + i;
		if (partp->mbrp_start == 0 && partp->mbrp_size == 0) {
			continue;
		}

		/* Save to Disk partition? */
		if (partp->mbrp_typ == 0xA0) {
			phpart = i;
			phsize = partp->mbrp_size - 2;
			break;
		}
	}

	if (phpart == -1) {
		warn("there is no Save to Disk partitions");
		exit(1);
	}

	create_header(phsize);
	do_format(phsize);

	printf("The phformat.dump was created.\n");
	printf("Run following command as root if it seems be OK.\n");
	printf("# cp phformat.dump %ss%d\n", disk, phpart+1);

	exit(0);
}

