/*

Done:
	Added calc_skews to figure out skews for all tracks at once.
	This has the happy side effect of making 24-sector formats
	work on my 8272a system whereas it didn't before (perhaps due
	to the time required to calculate skews *during* formatting?)

	Added a few interesting verbosity levels

	verify_now changed to verify_later; verify_now is default

	Combined format_track_1 and format_track_2; once calc_skews
	was created, they took the same parameters and were always
	called in succession

	Add support for 300 Kbps 3.5" DD disks (13-14 sector equivalent) like 2m

	940707 DCN Fixed BYTE vs. CHAR handling of no_verify and verify_later
               Allowed use of 13/14 sector non-2m disks

	940710 AK Fixed support for 300Kbps 3.5" DD disks without 2m

	20021102 AK: switched verify_later to be default on. Newer
	kernels (2.4.x) cannot refrain from reading ahead, even if
	BLKRA is set to 0, and this will cause failure when verifying
	during formatting, because read-ahead will attempt to access
	tracks that are not _yet_ formatted

Todo:
-	Allow reversing cylinder order, or perhaps have option to try as many
	cylinders as happen to work (as in 2m).  Currently, if too many cylinders
	are attempted it won't fail until the very end
 */
#include <sys/types.h>
#ifdef HAVE_SYS_SYSMACROS_H
# include <sys/sysmacros.h>
#endif
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/fd.h>
#include <linux/fdreg.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <linux/major.h>
#include <errno.h>
#include "enh_options.h"
#include "mediaprm.h"
#include "fdutils.h"
#include "oldfdprm.h"
#include "superformat.h"

int fm_mode=0;

struct defaults {
	char density;
	struct {
		int sect;
		int rate;
	} fmt[6];
} drive_defaults[] = {
{ DENS_UNKNOWN, { {0, 0}, {0, 0} }},
{ DENS_DD, { {0, 0}, { 0, 0}, {9, 2}, { 0, 0}, {0, 0}, {0, 0} } },
{ DENS_HD, { {0, 0}, { 0, 0}, {9, 1}, { 0, 0}, {15, 0}, {0, 0} } },
{ DENS_DD, { {0, 0}, { 0, 0}, {9, 2}, { 0, 0}, {0, 0}, {0, 0} } },
{ DENS_HD, { {0, 0}, { 0, 0}, {9, 2}, { 0, 0}, {18, 0}, {0, 0} } },
{ DENS_ED, { {0, 0}, { 0, 0}, {9, 2}, { 0, 0}, {18, 0}, {36, 0x43} } },
{ DENS_ED, { {0, 0}, { 0, 0}, {9, 2}, { 0, 0}, {18, 0}, {36, 0x43} } } };
int header_size=62;
int index_size=146; 


char floppy_buffer[24 * 512];
int verbosity = 3;
static char noverify = 0;
static char noformat = 0;
static char dosverify = 0;
static char verify_later = 0;
short stretch;
int cylinders, heads, sectors;
int begin_cylinder, end_cylinder;
int head_skew=1024, cylinder_skew=1536, absolute_skew=0;
int use_2m=0;
int lskews[MAX_TRACKS][MAX_HEADS] = {{0}};
int findex[MAX_TRACKS][MAX_HEADS] = {{0}};
int mask= 0;

char *error_msg[22]={
"Missing Data Address Mark",
"Bad cylinder",
"Scan not satisfied",
"Scan equal hit",
"Wrong cylinder",
"CRC error in data field",
"Control Mark = deleted",
0,

"Missing Address Mark",
"Write Protect",
"No Data - unreadable",
0,
"Overrun",
"CRC error in data or address",
0,
"End Of Cylinder",

0,
0,
0,
"Not ready",
"Equipment check error",
"Seek end" };

int send_cmd(int fd,struct floppy_raw_cmd *raw_cmd, char *message)
{
	int i;
	int code;
	int retries;
	struct floppy_raw_cmd tmp;
	
	retries=0;
	tmp = *raw_cmd;
 repeat:
	*raw_cmd = tmp;
	if ( ioctl( fd, FDRAWCMD, raw_cmd) < 0 ){
		if ( errno == EBUSY ){
			fprintf(stderr,"FDC busy, sleeping for a second\n");
			sleep(1);
			goto repeat;
		}
		perror(message);
#if 0
		printf("the final skew is %d\n", skew );
#endif
		exit(1);
	}

	if ( raw_cmd->reply_count ){
		switch( raw_cmd->reply[0] & 0xc0 ){
		case 0x40:
			/* ignore EOC */
			if((raw_cmd->reply[0] & ~0x4)== 0x40 &&
			   (raw_cmd->reply[1] == 0x80) &&
			   (raw_cmd->reply[2] == 0)) {
				/* OK, we merely reached the end of 
				 * our cylinder */
				break;
			}

			if((raw_cmd->reply[0] & ~0x4)== 0x40 &&
			   raw_cmd->reply[1] == 0x10 &&
			   raw_cmd->reply[2] == 0x00) {
				fprintf(stderr,"Overrun, pausing for a second\n");
				sleep(1);
				goto repeat;
			}

			fprintf(stderr,"error during command execution\n");
			if ( raw_cmd->reply[1] & ST1_WP ){
				fprintf(stderr,"The disk is write protected\n");
				exit(1);
			}
			fprintf(stderr,"   ");
			for (i=0; i< raw_cmd->cmd_count; i++)
				fprintf(stderr,"%2.2x ", (int)raw_cmd->cmd[i] );
			printf("\n");
			for (i=0; i< raw_cmd->reply_count; i++)
				fprintf(stderr,"%2.2x ", (int)raw_cmd->reply[i] );
			fprintf(stderr,"\n");
			code = (raw_cmd->reply[0] <<16) + 
				(raw_cmd->reply[1] << 8) + 
					raw_cmd->reply[2];
			for(i=0; i<22; i++){
				if ( (code & ( 1 << i )) && error_msg[i])
					fprintf(stderr,"%s\n", error_msg[i]);
			}
			printf("cylinder=%d head=%d sector=%d size=%d\n",
			       raw_cmd->reply[3],
			       raw_cmd->reply[4],
			       raw_cmd->reply[5],
			       raw_cmd->reply[6]);
			return -1;
			break;
		case 0x80:
			fprintf(stderr,"invalid command given\n");
			exit(1);
		case 0xc0:
			fprintf(stderr,"abnormal termination caused by polling\n");
			exit(1);
		case 0:
			/* OK */
			break;
		}
	}
	return 0;
}

int floppy_read(struct params *fd, void *data, int cylinder, 
				int head, int sectors)
{
	int n,m;
	if (lseek(fd->fd, (cylinder * heads + head) * sectors * 512,
		  SEEK_SET) < 0) {
		perror("lseek");
		return -1;
	}
	m = sectors * 512;
	while(m>0) {
		/* read until we have read everything we should */
		n=read(fd->fd, data, m);
		if ( n < 0 ) {
			perror("read");
			return -1;
		}
		if(n== 0) {
			fprintf(stderr, "Error, %d bytes remaining\n", m);
			return -1;
		}
		m -= n;
	}
	return 0;
}


int floppy_write(struct params *fd, void *data, 
				 int cylinder, int head, int sectors)
{
	int n,m;
	if (lseek(fd->fd, (cylinder * heads + head) * sectors * 512,
		  SEEK_SET) < 0) {
		perror("lseek");
		return -1;
	}
	m = sectors * 512;
	while(m>0) {
		/* write until we have write everything we should */
		n=write(fd->fd, data, m);
		if ( n < 0 ) {
			perror("write");
			return -1;
		}
		if(n== 0) {
			fprintf(stderr, "Error, %d bytes remaining\n", m);
			return -1;
		}
		m -= n;
	}
	return 0;
}

int floppy_verify(int superverify, struct params *fd, void *data, 
				  int cylinder, int head, int sectors)
{
	if(floppy_read(fd, data, cylinder, head, sectors))
		return -1;
	
	if(superverify) {
		/* write, and then read again */
		memset(data, sectors * 512, 0x55);
		if(floppy_write(fd, data, cylinder, head, sectors))
			return -1;
		ioctl(fd->fd, FDFLUSH);
		if(floppy_read(fd, data, cylinder, head, sectors))
			return -1;
	}
	return 0;
}

static int rw_track(struct params *fd, int cylinder, int head, int mode);

/* format_track. Does the formatting proper */
int format_track(struct params *fd, int cylinder, int head, int do_skew)
{
	format_map_t *data;
	struct floppy_raw_cmd raw_cmd;
	int offset;
	int i;
	int nssect;      
	int skew;
	
	data = (format_map_t *) floppy_buffer;

	/* place "fill" sectors */
	for (i=0; i<fd->nssect*2+1; ++i){
		data[i].sector = 128+i;
		data[i].size = /*fd->sizecode*/7;
		data[i].cylinder = cylinder;
		data[i].head = head;
	}

	if(do_skew) {
		fd += findex[cylinder][head];
		skew = fd->min + lskews[cylinder][head] * fd->chunksize;
		assert(skew >= fd->min);
		assert(skew <= fd->max);		
	} else
		skew = 0;

	/* place data sectors */
	nssect = 0;
	for (i=0; i<fd->dsect; ++i){
		offset = fd->sequence[i].offset + lskews[cylinder][head];
		offset = offset % fd->nssect;
		data[offset].sector = fd->sequence[i].sect - fd->zeroBased;
		data[offset].size = fd->sequence[i].size;
		data[offset].cylinder = cylinder;
		data[offset].head = head;
		if ( offset >= nssect )
			nssect = offset+1;
	}	
	if ( (nssect-1) * fd->chunksize > fd->raw_capacity - header_size - index_size){
		printf("Sector too far out %d*%d > %d-%d-%d!\n",
		       nssect , fd->chunksize, fd->raw_capacity , header_size,
		       index_size);
		exit(1);
	}

	/* debugging */
	if (verbosity == 9){
		printf("chunksize=%d\n", fd->chunksize);
		printf("sectors=%d\n", nssect);
		for (i=0; i<nssect; ++i)
			printf("%2d/%d, ", data[i].sector, data[i].size);
		printf("\n");
	}

	/* prepare command */
	raw_cmd.data = floppy_buffer;
	raw_cmd.length = nssect * sizeof(format_map_t);
	raw_cmd.cmd_count = 6;
	raw_cmd.cmd[0] = FD_FORMAT & ~fm_mode;
	raw_cmd.cmd[1] = (head << 2 | ( fd->drive & 3)) ^
	    (fd->swapSides ? 4 : 0);
	raw_cmd.cmd[2] = fd->sizecode;
	raw_cmd.cmd[3] = nssect;
	raw_cmd.cmd[4] = fd->fmt_gap;
	raw_cmd.cmd[5] = 0;
	raw_cmd.flags = FD_RAW_WRITE | FD_RAW_INTR | FD_RAW_SPIN | 
		FD_RAW_NEED_SEEK | FD_RAW_NEED_DISK;
	raw_cmd.track = cylinder << stretch;
	raw_cmd.rate = fd->rate & 0x43;

	/* first pass */
	if (verbosity >= 6)
		printf("formatting...\n");
	if(send_cmd(fd->fd, & raw_cmd, "format"))
		return -1;

	memset(floppy_buffer, 0, sizeof(floppy_buffer));
	if ( !fd->need_init && fd->sizecode)
		return 0;

	if (verbosity >= 6)
		printf("initializing...\n");
	return rw_track(fd, cylinder, head, 1);
}

/* format_track. Does the formatting proper */
static int rw_track(struct params *fd, int cylinder, int head, int mode)
{
	int i;
	int cur_sector;
	int retries;
	struct floppy_raw_cmd raw_cmd;

	cur_sector = 1 - fd->zeroBased;

	for (i=MAX_SIZECODE-1; i>=0; --i) {
		if ( fd->last_sect[i] <= cur_sector + fd->zeroBased)
			continue;
		retries=0;
	retry:
		/* second pass */
		raw_cmd.data = floppy_buffer;
		raw_cmd.cmd_count = 9;
		raw_cmd.cmd[0] =
			(mode ? FD_WRITE : FD_READ) & ~fm_mode & ~0x80;
		raw_cmd.cmd[1] = (head << 2 | ( fd->drive & 3)) ^
		    (fd->swapSides ? 4 : 0);
		raw_cmd.cmd[2] = cylinder;
		raw_cmd.cmd[3] = head;
		raw_cmd.cmd[4] = cur_sector;
		raw_cmd.cmd[5] = i;
		raw_cmd.cmd[6] = fd->last_sect[i] - 1 - fd->zeroBased;
		raw_cmd.cmd[7] = fd->gap;
		if ( i )
			raw_cmd.cmd[8] = 0xff;
		else
			raw_cmd.cmd[8] = 0xff;
		raw_cmd.flags = (mode ? FD_RAW_WRITE : FD_RAW_READ) | 
			FD_RAW_INTR | FD_RAW_SPIN |
			FD_RAW_NEED_SEEK | FD_RAW_NEED_DISK;
		raw_cmd.track = cylinder << stretch;
		raw_cmd.rate = fd->rate & 0x43;

		raw_cmd.length = (fd->last_sect[i] - 
				  fd->zeroBased - 
				  cur_sector) * 128 << i;
		/* debugging */
		if (verbosity == 9)
			printf("%s %ld sectors of size %d starting at %d\n",
			       mode ? "writing" : "reading",
			       raw_cmd.length / 512, i, cur_sector);
		if(send_cmd(fd->fd, & raw_cmd, 
			    mode ? "format" : "verify")){
			if ( !retries && mode && (raw_cmd.reply[1] & ST1_ND) ){
				cur_sector = raw_cmd.reply[5];
				retries++;
				goto retry;
			}
			return -1;
		}
		cur_sector = fd->last_sect[i];
	}
	return 0;
}



void print_formatting(int cylinder, int head)
{
	switch(verbosity) {
		case 0:
			break;
		case 1:
			if (!head) {
				printf(".");
				fflush(stdout);
			}
			break;
		case 2:
			if (head)
				printf("\b=");
			else
				printf("-");
			fflush(stdout);
			break;
		case 3:
		case 4:
			printf("\rFormatting cylinder %2d, head %d ",
				cylinder, head);
			fflush(stdout);
			break;
		default:
			printf("formatting cylinder %d, head %d\n",
				cylinder, head);
			break;
	}
}

void print_verifying(int cylinder, int head)
{
	if (verbosity >= 5) {
		printf("verifying cylinder %d head %d\n",
			cylinder, head);
	} else if (verbosity >= 3) {
		printf("\r Verifying cylinder %2d, head %d ", cylinder, head);
		fflush(stdout);
	} else if (verbosity == 2) {
		if (!verify_later && !dosverify) {
			if (head)
				printf("\b+");
			else
				printf("\bx");
		} else {
			if (head)
				printf("\b+");
			else
				printf("x");
		}
		fflush(stdout);
	}
}


void old_parameters()
{



}

#define DRIVE_DEFAULTS (drive_defaults[drivedesc.type.cmos])

int main(int argc, char **argv)
{
	int nseqs; /* number of sequences used */
	char env_buffer[10];
	struct floppy_struct parameters;
	struct params fd[MAX_SECTORS], fd0;
	int ch,i;
	short density = DENS_UNKNOWN;
	char drivename[10];

	int have_geom = 0;
	int margin=50;
	int deviation=-3100;
	int warmup = 40; /* number of warmup rotations for measurement */

	int cylinder, head, interleave;
	int gap;
	int final_gap;
	int chunksize;
	int sizecode=2;
	int error;
	int biggest_last = 0;
	int superverify=0;

	char command_buffer[80];
	char twom_buffer[6];
	char *progname=argv[0];

	short retries;
	short zeroBased=0;
	short swapSides=0;
	int n,rsize;
	char *verify_buffer = NULL;
	char dosdrive;
	drivedesc_t drivedesc;
	struct floppy_struct geometry;
	int max_chunksize = 128*128+62+256;

	struct enh_options optable[] = {
	{ 'D', "dosdrive", 1, EO_TYPE_CHAR, 0, SET_DOSDRIVE,
		(void *) &dosdrive,
		"set the dos drive" },

	{ 'v', "verbosity", 1, EO_TYPE_LONG, 0, 0,
		(void *) &verbosity,
		"set verbosity level" },

	{ 'f', "noverify", 0, EO_TYPE_BYTE, 1, 0,
		(void *) &noverify,
		"skip verification" },

	{ '\0', "print-drive-deviation", 0, EO_TYPE_BYTE, 1, 0,
		(void *) &noformat,
		"print deviation, do not format " },

	{ 'B', "dosverify", 0, EO_TYPE_BYTE, 1, 0,
		(void *) &dosverify,
		"verify disk using mbadblocks" },

	{ 'V', "verify_later", 1, EO_TYPE_BYTE, 1, 0,
		(void *) &verify_later,
		"verify floppy after all formatting is done" },

	{ 'b', "begin_cylinder", 1, EO_TYPE_LONG, 0, 0,
		(void *) &begin_cylinder,
		"set cylinder where to begin formatting" },

	{ 'e', "end_cylinder", 1, EO_TYPE_LONG, 0, SET_ENDTRACK,
		(void *) &end_cylinder,
		"set cylinder where to end formatting" },

	{ 'G', "fmt_gap", 1, EO_TYPE_LONG, 0, SET_FMTGAP,
		(void *) &gap,
		"set the formatting gap" },

	{ 'F', "final_gap", 1, EO_TYPE_LONG, 0, SET_FINALGAP,
		(void *) &final_gap,
		"set the final gap" },

	{ 'i', "interleave", 1, EO_TYPE_LONG, 0, SET_INTERLEAVE,
		(void *) &interleave,
		"set the interleave factor" },

	{ 'c', "chunksize", 1, EO_TYPE_LONG, 0, SET_CHUNKSIZE,
		(void *) &chunksize,
		"set the size of the \"chunks\" (small auxiliary sectors used for formatting). This size must be in the interval [191,830]" },

	{ '\0', "max-chunksize", 1, EO_TYPE_LONG, 0, 0,
		(void *) &max_chunksize,
		"set a maximal size for the \"chunks\" (small auxiliary sectors used for formatting)." },

	{ 'g', "gap", 1, EO_TYPE_LONG, 0, 0,
		(void *) &fd[0].gap,
		"set the r/w gap" },

	{'\0', "absolute_skew", 1, EO_TYPE_LONG, 0, 0,
		(void *) &absolute_skew,
		"set the skew used at the beginning of formatting" },

	{'\0', "head_skew", 1, EO_TYPE_LONG, 0, 0,
		(void *) &head_skew,
		"set the skew to be added when passing to the second head" },

	{'\0', "cylinder_skew", 1, EO_TYPE_LONG, 0, 0,
		(void *) &cylinder_skew,
		"set the skew to be added when passing to another cylinder" },

	{'\0', "aligned_skew", 0, EO_TYPE_BITMASK_LONG, ALSKEW, 0,
		(void *) &fd[0].flags,
		"select sector aligned skewing" },

	{'w', "warmup", 1, EO_TYPE_LONG, 0, 0,
		 (void *) &warmup,
		 "number of warmup rotations for before measurement of raw drive capacity"},


	{ 'd', "drive", 1, EO_TYPE_STRING, O_RDWR, 0,
		(void *) &fd[0].name,
		"set the target drive (obsolete.  Specify drive without -d instead)" },
	{'\0', "deviation", 1, EO_TYPE_LONG, 0, SET_DEVIATION,
		 (void *) &deviation,
		 "selects the deviation (in ppm) from the standard rotation speed (obsolete. Use " DRIVEPRMFILE " instead"},
	{'m', "margin", 1, EO_TYPE_LONG, 0, SET_MARGIN,
		 (void *) &margin,
		 "selects the margin to be left at the end of the physical cylinder (obsolete. Use " DRIVEPRMFILE " instead)" },
	{ 's', "sectors", 1, EO_TYPE_LONG, 0, SET_SECTORS,
		(void *) &sectors,
		"set number of sectors (obsolete. Use a media description instead)" },

	{ 'H', "heads", 1, EO_TYPE_LONG, 0, SET_HEADS,
		(void *) &heads,
		"set the number of heads (obsolete. Use a media description instead)" },

	{ 't', "cylinders", 1, EO_TYPE_LONG, 0, SET_CYLINDERS,
		(void *) &cylinders,
		"set the number of cylinders (obsolete. Use a media description instead)" },

	{'\0', "stretch", 1, EO_TYPE_LONG, 0, SET_STRETCH,
		(void *) &stretch,
		"set the stretch factor (how spaced the cylinders are from each other) (obsolete. Use a media description instead)" },

	{ 'r', "rate", 1, EO_TYPE_LONG, 0, SET_RATE,
		(void *) &fd[0].rate,
		"set the data transfer rate (obsolete. Use a media description instead)" },

	{ '2', "2m", 0, EO_TYPE_LONG, 255, SET_2M,
		(void *) &use_2m,
		"format disk readable by the DOS 2m shareware program (obsolete. Use a media description instead)" },

	{ '1', "no2m", 0, EO_TYPE_LONG, 0, SET_2M,
		(void *) &use_2m,
		"don't use 2m formatting (obsolete.  Use a media description instead)" },

	{ '\0', "fm", 0, EO_TYPE_SHORT, 0x40, 0,
		(void *) &fm_mode,
		"chose fm mode (obsolete.  Use a media description instead)" },


	{ '\0', "dd", 0, EO_TYPE_SHORT, DENS_DD, 0,
		(void *) &density,
		"chose low density (obsolete.  Use a media description instead)" },

	{ '\0', "hd", 0, EO_TYPE_SHORT, DENS_HD, 0,
		(void *) &density,
		"chose high density (obsolete.  Use a media description instead)" },

	{ '\0', "ed", 0, EO_TYPE_SHORT, DENS_ED, 0,
		(void *) &density,
		"chose extra density (obsolete.  Use a media description instead)" },

	{ 'S', "sizecode", 1, EO_TYPE_LONG, 0, SET_SIZECODE,
		(void *) &sizecode,
		"set the size code of the data sectors. The size code describes the size of the sector, according to the formula size=128<<sizecode. Linux only supports sectors of 512 bytes and bigger. (obsolete.  Use a media description instead)" },

	{ '\0', "biggest-last", 0, EO_TYPE_SHORT, 1, 0,
		(void *) &biggest_last,
		"for MSS formats, make sure that the biggest sector is the last on the track.  This makes superformat more reliable if your drive is slightly out of spec" },

	{ '\0', "superverify", 0, EO_TYPE_SHORT, 1, 0,
		(void *) &superverify,
		"During the verification step, write a pattern of 0x55 to the track, and check whether it can still be read back" },


	{ '\0', "zero-based", 0, EO_TYPE_SHORT, 1, 0,
	  	(void *) &zeroBased,
	  	"Start numbering sectors from 0 instead of 1 (not readable by normal I/O)" },

	{ '\0', 0 }
	};

	/* default values */
	cylinders = 80; heads = 2; sectors = 18;
	fd[0].fd = -1; fd[0].rate = 0; fd[0].flags = 0;
	fd[0].gap = 0x1b;
	dosdrive='\0';
	fd[0].name = 0;
	gap=0;
	sizecode = 2;

	while( (ch=getopt_enh(argc, argv, optable,
			0, &mask, "drive") ) != EOF) {
		if (ch == '?')
			exit(1);
		fprintf(stderr,"unhandled option %d\n", ch);
		exit(1);
	}

	/* sanity checking */
	if (sizecode < 0 || sizecode >= MAX_SIZECODE) {
		fprintf(stderr,"Bad sizecode %d\n", sizecode);
		print_usage(progname,optable, "");
		exit(1);
	}

	if ( gap < 0 ){
		fprintf(stderr,"Fmt gap too small: %d\n", gap);
		print_usage(progname,optable, "");
		exit(1);
	}

	if (sectors <= 0 || cylinders <= 0 || heads <= 0) {
		fprintf(stderr,"bad geometry s=%d h=%d t=%d\n",
			sectors, heads, cylinders);
		print_usage(progname,optable, "");
		exit(1);
	}

	argc -= optind;
	argv += optind;
	if(argc) {
		fd[0].name = argv[0];
		argc--;
		argv++;
	}

	if (! fd[0].name){
		fprintf(stderr,"Which drive?\n");
		print_usage(progname,optable, "");
		exit(1);
	}

	while(1) {
		fd[0].fd = open(fd[0].name, O_RDWR | O_NDELAY | O_EXCL);
		
		/* we open the disk wronly/rdwr in order to check write 
		 * protect */
		if (fd[0].fd < 0) {
			perror("open");
			exit(1);
		}

		if(parse_driveprm(fd[0].fd, &drivedesc))
			exit(1);
		
		fd[0].drive = drivedesc.drivenum;
		fd[0].drvprm = drivedesc.drvprm;

		if(minor(drivedesc.buf.st_rdev) & 0x7c) {
			if(fd[0].name == drivename) {
				fprintf(stderr,
					"%s has bad minor/major numbers\n",
					fd[0].name);
				exit(1);
			}
			/* this is not a generic format device. Close it,
			 * and open the proper device instead */
			if(argc == 0)
				ioctl(fd[0].fd, FDGETPRM, &geometry);
			have_geom = 1;
			close(fd[0].fd);
			snprintf(drivename,9,"/dev/fd%d", fd[0].drive);
			fd[0].name = drivename;
			continue;
		}
		break;
	}




	if(have_geom  ||
	   !parse_mediaprm(argc, argv, &drivedesc, &geometry) ||
	   !parse_fdprm(argc-2, argv+2, &geometry)) {
		if(argc > 0)
			have_geom = 1;
	} else {
		fprintf(stderr,"Syntax error in format description\n");
		exit(1);
	}


	if(have_geom) {
		if(mask & (SET_SECTORS | SET_CYLINDERS | 
			   SET_HEADS | SET_SIZECODE | SET_2M | SET_RATE)) {
			fprintf(stderr,
				"Cannot mix old style and new style geometry spec\n");
			exit(1);
		}

		sectors = geometry.sect;
		cylinders = geometry.track;
		heads = geometry.head;

		fd[0].rate = geometry.rate & 0x43;
		sizecode = (((geometry.rate & 0x38) >> 3) + 2) % 8;
		use_2m = (geometry.rate >> 2) & 1;
		switch(fd[0].rate) {
			case 0x43:
				density = DENS_ED;
				break;
			case 0x2:
			case 0x1:
				density = DENS_DD;
				break;
			case 0:
				density = DENS_HD;
				break;
		}
		stretch = geometry.stretch & 1;
#ifdef FD_ZEROBASED
		if(geometry.stretch & FD_ZEROBASED) {
			zeroBased = 1;
		}
#endif
#ifdef FD_SWAPSIDES
		if(geometry.stretch & FD_SWAPSIDES) {
			swapSides = 1;
		}
#endif
		mask |= SET_SECTORS | SET_CYLINDERS | 
			SET_SIZECODE | SET_2M | SET_RATE;
	} else {
		/* density */
		if ( (mask & SET_SECTORS ) && density == DENS_UNKNOWN){
			if ( sectors < 15 )
				density = DENS_DD;
			else if ( sectors < 25 )
				density = DENS_HD;
			else
				density = DENS_ED;
		}
		if (density == DENS_UNKNOWN) {
			density = DRIVE_DEFAULTS.density;
			if ( mask & SET_RATE ){
				for (i=0; i< density; ++i) {
					if(fd[0].rate == 
					   DRIVE_DEFAULTS.fmt[i].rate)
						density=i;
				}
			}
		} else {
			if (DRIVE_DEFAULTS.fmt[density].sect == 0) {
				fprintf(stderr,
					"Density %d not supported drive type %d\n",
					density, drivedesc.type.cmos);
				exit(1);
			}
		}

		/* rate */
		if (! ( mask & SET_RATE))
			fd[0].rate = DRIVE_DEFAULTS.fmt[density].rate;
		
		/* number of sectors */
		if (! (mask & SET_SECTORS))
			sectors =DRIVE_DEFAULTS.fmt[density].sect;
		if (! (mask & SET_CYLINDERS)) {
			if (fd[0].drvprm.tracks >= 80)
				cylinders = 80;
			else
				cylinders = 40;
		}

		if ( ! ( mask & SET_STRETCH )){
			if ( cylinders + cylinders < fd[0].drvprm.tracks)
				stretch = 1;
			else
				stretch = 0;
		}
	}

	fd[0].zeroBased = zeroBased;
#ifndef FD_ZEROBASED
	if(zeroBased) {
		noverify = 1;
	}
#endif
	
	fd[0].swapSides = swapSides;
		
	if (cylinders > fd[0].drvprm.tracks) {
		fprintf(stderr,"too many cylinder for this drive\n");
		print_usage(progname,optable,"");
		exit(1);
	}

	if (! (mask & SET_ENDTRACK ) || end_cylinder > cylinders)
		end_cylinder = cylinders;
	if(begin_cylinder >= end_cylinder) {
		fprintf(stderr,"begin cylinder >= end cylinder\n");
		exit(1);
	}

	fd0 = fd[0];
 repeat:
	/* capacity */	
	if (!have_geom && sectors >= 12 && fd[0].rate == 2)
		fd[0].rate = 1;
	switch(fd[0].rate & 0x3) {
	case 0:
		fd[0].raw_capacity = 500 * 1000 / 8 / fd[0].drvprm.rps;
		break;
	case 1:
		fd[0].raw_capacity = 300 * 1000 / 8 / fd[0].drvprm.rps;
		break;
	case 2:
		fd[0].raw_capacity = 250 * 1000 / 8 / fd[0].drvprm.rps;
		break;
	case 3:
		fd[0].raw_capacity = 1000 * 1000 / 8 / fd[0].drvprm.rps;
		break;
	}
	if (fd[0].rate & 0x40)
		header_size = 81;		
	else
		header_size = 62;

	if(! (mask & (SET_DEVIATION | SET_MARGIN)) &&
	   (drivedesc.mask & (1 << FE__DEVIATION))) {	       
		deviation = drivedesc.type.deviation;
		mask |= SET_DEVIATION;
	}

	if(mask & SET_DEVIATION) {
		mask &= ~SET_MARGIN;
		fd[0].raw_capacity +=  fd[0].raw_capacity * deviation / 1000000;
	} else if (mask & SET_MARGIN) {
		fd[0].raw_capacity -= margin;
	} else  {
		int old_capacity = fd[0].raw_capacity;

/*		printf("old capacity=%d\n", old_capacity);*/

		if(verbosity) {
			fprintf(stderr,"Measuring drive %d's raw capacity\n",
				fd[0].drive);
		}
		/* neither a deviation nor a margin have been given */
		fd[0].raw_capacity = measure_raw_capacity(fd[0].fd,
							  fd[0].drive,
							  fd[0].rate,
							  begin_cylinder,
							  warmup,
							  verbosity) / 16;
		if(verbosity) {
			fprintf(stderr,
				"In order to avoid this time consuming "
				"measurement in the future,\n"
				"add the following line to " DRIVEPRMFILE ":\n");
			fprintf(stdout,
				"drive%d: deviation=%d\n",
				fd[0].drive, 
				(fd[0].raw_capacity-old_capacity)*1000000/
				old_capacity);
			fprintf(stderr,
				"CAUTION: The line is drive and controller "
				"specific, so it should be\n" 
				"removed before installing a new "
				"drive %d or floppy controller.\n\n", 
				fd[0].drive);
		}
	}

	if(noformat)
	    return 0;

	/* FIXME.  Why is this needed? */
	fd[0].raw_capacity -= 30;

	fd0.raw_capacity = fd[0].raw_capacity ;

	if (verbosity == 9)
		printf("rate=%d density=%d sectors=%d capacity=%d\n",
			fd[0].rate, density, sectors, fd[0].raw_capacity);

	fd->chunksize = chunksize;
	fd->max_chunksize = max_chunksize;
	fd->preset_interleave = interleave;
	nseqs = compute_all_sequences(fd, sectors * 512, sizecode,
				      gap, mask, biggest_last);

	/* print all the stuff out */
	if (verbosity == 9) {
		for (i=0; i<fd[0].dsect; ++i)
			printf("s=%2d S=%2d o=%2d\n",
				fd[0].sequence[i].sect,
				fd[0].sequence[i].size,
				fd[0].sequence[i].offset);

		printf("fd[0].sizecode=%d\n", fd[0].sizecode);
		printf("fd[0].fmt_gap=%d\n", fd[0].fmt_gap);
		printf("fd[0].dsect=%d\n", fd[0].dsect);
		printf("fd[0].nssect=%d\n", fd[0].nssect);
	}
	if (sizecode > 2 && !(mask & SET_2M)) {
		use_2m = 1;
		if (fd[0].rate == 2) {
			fd[0].rate = 1;
			goto repeat;
		}
	}
	if (use_2m) {
		fd0.dsect = DRIVE_DEFAULTS.fmt[density].sect;
		compute_track0_sequence(&fd0);

		if (verbosity == 9){
			for (i=0; i< fd0.dsect; i++)
				printf("s=%2d S=%2d o=%2d\n",
					fd0.sequence[i].sect,
					fd0.sequence[i].size,
					fd0.sequence[i].offset);

			printf("fd[0].sizecode=%d\n", fd0.sizecode);
			printf("fd[0].fmt_gap=%d\n", fd0.fmt_gap);
			printf("fd[0].dsect=%d\n", fd0.dsect);
			printf("fd[0].nssect=%d\n", fd0.nssect);
		}
	}

	parameters.sect = sectors;
	parameters.head = heads;
	parameters.track = cylinders;
	parameters.size = cylinders * heads * sectors;
	parameters.stretch = stretch 
#ifdef FD_ZEROBASED
		| (zeroBased ? 4 : 0)
#endif
		| (swapSides ? 2 : 0);
	parameters.gap = fd[0].gap;
	if ( !use_2m)
		fd0.rate = fd[0].rate;
	parameters.rate = ( fd0.rate & ~(FD_SIZECODEMASK | FD_2M) ) |
		(((sizecode + 6 ) % 8 ) << 3) | (use_2m ? FD_2M : 0) ;
	parameters.spec1 = 0xcf;
	if(fd[0].fmt_gap < 1 )
		parameters.fmt_gap = 1;
	else if (fd[0].fmt_gap > 255 )
		parameters.fmt_gap = 0;
	else
		parameters.fmt_gap = fd[0].fmt_gap;
	if(ioctl(fd[0].fd, FDSETPRM, &parameters) < 0) {
		perror("set geometry");
		exit(1);
	}

	calc_skews(&fd0, fd, nseqs);

	if (!verify_later && !dosverify) {
		ioctl(fd[0].fd, FDFLUSH );
		verify_buffer = malloc( 512 * sectors * heads);
		if (! verify_buffer) {
			printf("out of memory error\n");
			exit(1);
		}
	}

	retries=0;
	/*ioctl(fd[0].fd, FDRESET, FD_RESET_IF_RAWCMD);*/
	for (cylinder=begin_cylinder; cylinder<end_cylinder;) {
		error=0;
		if ( retries >= 2)
			exit(1);
#if 0
		if(seek_floppy( fd, cylinder << stretch))
			exit(1);
#endif
		for (head=0; head<heads; ++head) {
			print_formatting(cylinder, head);
			if (!cylinder && !head && use_2m){
				/* 2m-style 1st track */
				if (format_track(&fd0, cylinder, head, 0) &&
					format_track(&fd0, cylinder, head, 0))
					exit(1);

			} else {
				/* Everything else */
				if (format_track(fd, cylinder, head, 1) &&
					format_track(fd, cylinder, head, 1))
					exit(1);
			}
		}
		retries++;
		if (!verify_later && !noverify && !dosverify) {
			for (head=0; head<heads; ++head) {
				print_verifying(cylinder, head);
				if (!cylinder && !head && use_2m){
					/* 2m-style 1st track */
					if (rw_track(&fd0, cylinder, head, 0) &&
					    rw_track(&fd0, cylinder, head, 0)){
						error=1;
						break;
					}
				} else {
					/* Everything else */
					if (rw_track(fd, cylinder, head, 0) &&
					    rw_track(fd, cylinder, head, 0)) {
						error=1;
						break;
					}
				}
			}
		}
		if (error){
			ioctl(fd->fd, FDRESET, FD_RESET_ALWAYS);
			continue;
		}
		retries = 0;
		++cylinder;
	}

	ioctl(fd[0].fd, FDFLUSH );
	close(fd[0].fd);

	if (! (mask & SET_DOSDRIVE ) && fd[0].drive < 2 && !zeroBased)
		dosdrive = fd[0].drive+'a';

	if (dosdrive) {
		if (verbosity >= 5)
			printf("calling mformat\n");
		if (use_2m)
			snprintf(twom_buffer, 5, "-2 %2d", fd0.dsect);
		else
			twom_buffer[0]='\0';
		snprintf(command_buffer, 79,
			"mformat -s%d -t%d -h%d -S%d -M512 %s %c:",
			sectors,
			/*use_2m ? sectors : sectors >> (sizecode - 2), */
			cylinders, heads, sizecode, twom_buffer, dosdrive);
		if (verbosity >= 3) {
			printf("\n%s\n", command_buffer);
		}
		snprintf(env_buffer, 9, "%d", (int)fd0.rate&3);
		setenv("MTOOLS_RATE_0", env_buffer,1);
		snprintf(env_buffer, 9, "%d", (int)fd->rate&3);
		setenv("MTOOLS_RATE_ANY", env_buffer,1);
		if(system(command_buffer)){
			fprintf(stderr,"\nwarning: mformat error\n");
			/*exit(1);*/  
			/* Do not fail, if mformat happens to not be 
			 * installed. The user might have wanted to make
			 * an ext2 disk for instance */
			dosverify = 0;
		}			
	} else {
		if(!zeroBased)
			fprintf(stderr,
				"\nwarning: mformat not called because DOS drive unknown\n");
		/*exit(1);*/
		dosverify = 0;
	}

	if (!noverify && verify_later) {
		fd[0].fd = open ( fd[0].name, O_RDONLY);
		if ( fd[0].fd < 0 ) {
			perror("open for verify");
			exit(1);
		}
		if (verbosity >= 7)
			printf("verifying\n");
		else if (verbosity == 2) {
			printf("\r");
			fflush(stdout);
		}
		verify_buffer = malloc( 512 * sectors * heads);
		if (! verify_buffer) {
			printf("out of memory error\n");
			exit(1);
		}
		lseek(fd[0].fd, 512 * begin_cylinder * heads, SEEK_SET );
		for (cylinder=begin_cylinder; cylinder<end_cylinder; ++cylinder)
			for (head=0; head<heads; ++head) {
				print_verifying(cylinder, head);
				rsize = 512 * sectors;
				while(rsize){
					n = read(fd[0].fd,verify_buffer,rsize);
					if ( n < 0){
						perror("read");
						fprintf(stderr, 
							"remaining %d\n", n);
						exit(1);
					}
					if ( n == 0 ){
						fprintf(stderr,"End of file\n");
						exit(1);
					}
					rsize-=n;
				}
			}
		close(fd[0].fd);
	}
	if (dosverify){
		snprintf(command_buffer, 79,
			"mbadblocks %c:", dosdrive);
		if (verbosity >= 3) {
			printf("\n%s\n", command_buffer);
		}
		if(system(command_buffer)){
			fprintf(stderr,"mbadblocks error\n");
			exit(1);
		}			
	}		

	printf("\n");
	return 0;
}
