/* This lowlevel formatter is based on knowledged gained from scsiinfo.c version 1.6
 * by Eric Youngdale - 11/1/93.  Version 1.0.
 * with extensions by Michael Weller (eowmob@exp-math.uni-essen.de) 11/23/94
 *
 * Version 1.0 Michael Weller (eowmob@exp-math.uni-essen.de) 12/31/96
 *
 * Partioning knowledge and some tiny macros got from:
 *
 * fdisk.c -- Partition table manipulator for Linux.
 *
 * Copyright (C) 1992  A. V. Le Blanc (LeBlanc@mcc.ac.uk)
 *
 * Modified by faith@cs.unc.edu
 * Modified by Michael Bischoff <i1041905@ws.rz.tu-bs.de> or <mbi@mo.math.nat.tu-bs.de>
 * Modified by martin@cs.unc.edu:
 * Modified by leisner@sdsp.mc.xerox.com
 * Modified by esr@snark.thyrsus.com
 * Modified by Daniel Quinlan <quinlan@yggdrasil.com>
 * Modified by fasten@cs.bonn.edu:
 *
 * This program is free software.  You can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation: either version 1 or
 * (at your option) any later version.
 *
 */

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include <errno.h>
#include <alloca.h>
#include <time.h>
#include <signal.h>
#include <dirent.h>
#include <mntent.h>
#include <fcntl.h>
#include <termios.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/swap.h>
#include <sys/mount.h>
#include <linux/hdreg.h>

struct partition {
	unsigned char boot_ind;		/* 0x80 - active */
	unsigned char head;		/* starting head */
	unsigned char sector;		/* starting sector */
	unsigned char cyl;		/* starting cylinder */
	unsigned char sys_ind;		/* What partition type */
	unsigned char end_head;		/* end head */
	unsigned char end_sector;	/* end sector */
	unsigned char end_cyl;		/* end cylinder */
	unsigned int start_sect;	/* starting sector counting from 0 */
	unsigned int nr_sects;		/* nr of sectors in partition */
} __attribute__((packed));

#define TEST_UNIT_READY	(0x00)
#define FORMAT_UNIT	(0x04)
#define NOT_READY	(0x01)

#define VERSION "scsiformat V1.0 08/23/97 by Michael Weller."

#define SECTOR_SIZE 512
#define WAIT_DISK 10		/* Timeouts for flush and rrpart */

#undef DONT_FORMAT		/* define for debugging */
#define BLOCKING_ONLY		/* I could not try this, use at own risk */

int fd, ttyfd = 2;
FILE *ttyFILE;

pid_t child = -1;
time_t start;
char tmpfname[] = "/var/run/scsiformat.XX:XX:XX:XX:XXXXXXXX";
char *devicename, *linebuf, *givendevice;
long scsiunid[2];

char par_xinterface = 0;
char par_checkmode = 0;
char par_blockmode = 0;
char par_erasedefs = 0;
char par_deflistform = 0;
char par_fov = 0;
char par_force = 0;
char par_disprim = 0;
char par_discert = 0;
char par_stop = 0;
char par_debug = 0;
char par_dissav = 0;
char par_geom = 0;
unsigned char part_type = 0x83;
enum {
    DOSPART, TIGHTPART, NOPART
} par_dopart = NOPART;

unsigned par_heads, par_sectors;
unsigned par_partsize = 99999;
unsigned par_checkint = 5;
unsigned par_estimate;
unsigned par_patternlen = 4;
unsigned par_defectlen = 0;
unsigned par_interleave = 0;
unsigned char par_initpattern[65536 + 5];
unsigned int par_defects[65536 / sizeof(unsigned long) + 3];

struct winsize wins;

#define set_hsc(h,s,c,sector) { unsigned long my_sector = sector;	\
				\
				s = my_sector % par_sectors + 1;	\
				my_sector /= par_sectors;		\
				h = my_sector % par_heads;		\
				my_sector /= par_heads;			\
				c = my_sector & 0xff;			\
				s |= (my_sector >> 2) & 0xc0;		\
			      }

void sense_err(int status, unsigned char *buffer);
void catch_term(void);
void mk_tmpfile(void);
void rm_tmpfile(void);
void exit_time(void);
void do_checkmode(int block);
void init_partition(void);

void dump(FILE * fh, void *buffer, unsigned int length)
{
    unsigned int i;

    for (i = 0; i < length; i++) {
	fprintf(fh, "%02x ", (unsigned int) ((unsigned char *) buffer)[i]);
	if (i % 16 == 15) {
	    fprintf(fh, "\n");
	}
    }
    fprintf(fh, "\n");
}

void argperr(char *format,...)
{
    int locerr = errno;
    va_list params;

    va_start(params, format);
    vfprintf(stderr, format, params);
    va_end(params);
    fprintf(stderr, ": %s\n", strerror(locerr));
}

int send_scsi_cmd(int fileno, int cmd_len, unsigned char* buffer)
{
    int status;
#ifdef SG_IO
    if (use_sgio) {
	sg_io_hdr_t sgh;
	char sense_buffer[16];
	int dir = SG_DXFER_NONE;
	int in_len = *((int*)buffer);
	int out_len = *(((int*)buffer)+1);
	if (in_len != 0)
	    dir = SG_DXFER_FROM_DEV;
	else if (out_len != 0)
	    dir = SG_DXFER_TO_DEV;
	memset(&sgh, 0, sizeof(sgh));
	sgh.interface = 'S';
	sgh.dxfer_direction = dir;
	sgh.dxferp = buffer+(dir == SG_DXFER_TO_DEV ? cmd_len : 0)+8;
	sgh.dxfer_len = dir == SG_DXFER_TO_DEV ? in_len : out_len;
	sgh.cmd_len = cmd_len;
	sgh.cmdp = buffer+8;
	sgh.sbp = sense_buffer;
	sgh.m_sb_len = sizeof(sense_buffer);
	sgh.timeout = 2000; /* 2s */
	status = ioctl(fileno, SG_IO, &sgh);
	if (status < 0) {
	    /* scsi error: dump sense buffer into output area */
	    memcpy(buffer+8, sense_buffer, sgh.sb_len_wr);
	    /* return a status similar to the one returned by legacy ioctl */
	    status = sgh.driver_status << 24 | sgh.host_status << 16 | sgh.msg_status << 8 || sgh.masked_status;
	}
	else {
	    status = 0;
	}
    }
    else
#else
	status = ioctl(fileno, 1 /* SCSI_IOCTL_SEND_COMMAND */, buffer);
#endif
    return status;
}

/* return -1 for ready, -2 for no progress indication available) and
   progress indicator else. */
/* BUG/MISFEATURE: The linux device driver stores the sense data into another
   location which is only returned on error conditions. Alas, REQUEST_SENSE
   does not fail. We use TEST_UNIT_READY instead. When it returns ready we are
   sure that we are NOT currently formatting... */

int request_sense(void)
{
    unsigned char buffer[100];
    int status;
    unsigned char *cmd;

    memset(buffer, -1, sizeof(buffer));

    *((int *) buffer) = 6;	/* length of input data */
    *(((int *) buffer) + 1) = 18;	/* length of output buffer */

    cmd = (unsigned char *) (((int *) buffer) + 2);

    cmd[0] = TEST_UNIT_READY;
    cmd[1] = 0x00;		/* LUN / (reserved) */
    cmd[2] = 0x00;		/* (reserved) */
    cmd[3] = 0x00;		/* (reserved) */
    cmd[4] = 0x00;		/* (reserved) */
    cmd[5] = 0x00;		/* (reserved) */

    if (par_debug) {
	printf("Sending TEST_UNIT_READY command:\n");
	dump(stdout, ((int *) buffer) + 2, *((int *) buffer));
    }
    status = send_scsi_cmd(fd, 6, buffer);
    if ((status == EAGAIN) || (status == EBUSY))
	return -2;
    if (status < 0) {
	argperr("scsiformat (sending TEST_UNIT_READY to %s)", devicename);
	exit(1);
    }
    if (par_debug) {
	printf("Got SCSI status %02x and extended sense information:\n", status);
	dump(stdout, buffer + 8, 18);
    }
    if (!status)
	return -1;		/* device is ready (thus not formatting) */
    if (((status & 0xFFF0000) == 0x8000000) && ((cmd[0] & ~1) == 0x70)
	&& ((cmd[2] & 0x0f) == NOT_READY)) {
	if ((cmd[7] >= 10) && (cmd[15] & 0x80))
	    return (cmd[16] << 8) | cmd[17];
	else
	    return -2;
    }
    sense_err(status, buffer + 8);
    /* not reached */
    return -2;
}

void mk_head(void)
{
    int i;

    printf("Started");
    i = 7 + 13;
    do {
	putchar(' ');
	i++;
    } while (i < wins.ws_col);
    printf("Estimated End\n");
}

void Xestimate(time_t start, time_t end, int percentage)
{
    struct tm *spltime;
    unsigned int i;

    spltime = localtime(&start);
    printf("%d:%02d:%02d ", spltime->tm_hour, spltime->tm_min, spltime->tm_sec);
    spltime = localtime(&end);

    if (percentage >= 0) {
	i = (1000 * percentage + 32767) / 65535;
	printf("%u.%03u ", i / 1000, i % 1000);
    } else {
	printf("- ");
    }
    end = time(NULL) - start;
    if (end >= 3600) {
	printf("%dh", (int) end / 3600);
	end %= 3600;
    }
    if (end >= 60) {
	printf("%dm", (int) end / 60);
	end %= 60;
    }
    printf("%ds", (int) end);

    if (percentage >= 0) {
	printf(" %d:%02d:%02d\n", spltime->tm_hour, spltime->tm_min, spltime->tm_sec);
    } else {
	puts(" unknown");
    }
}

void mk_statusbar(time_t start, time_t end, int percentage)
{
    char buf[1 + 9 + 1 + 4 + 9 + 1 + 10];
    /*       (XXXXXXXXX.X%, XXhXXmXXs) */
    struct tm *spltime;
    unsigned int i, len;

    linebuf[wins.ws_col] = 0;
    memset(linebuf, ' ', wins.ws_col);

    if (percentage >= 0) {
	i = wins.ws_col - 18;
	len = (i * percentage + 32767) / 65535;
	if (len > wins.ws_col - 18)
	    len = wins.ws_col - 18;
	if (len)
	    memset(linebuf + 9, '*', len);
    }
    spltime = localtime(&start);
    sprintf(linebuf, "%2d:%02d:%02d", spltime->tm_hour, spltime->tm_min, spltime->tm_sec);
    linebuf[8] = ' ';
    if (percentage >= 0) {
	spltime = localtime(&end);
	sprintf(linebuf + wins.ws_col - 8, "%2d:%02d:%02d", spltime->tm_hour, spltime->tm_min,
		spltime->tm_sec);

	i = (1000 * percentage + 32767) / 65535;
	sprintf(buf, "(%u.%u%%, ", i / 10, i % 10);
    } else {
	strcpy(linebuf + wins.ws_col - 7, "unknown");
	strcpy(buf, "(Running since ");
    }
    end = time(NULL) - start;
    if (end >= 3600) {
	sprintf(strchr(buf, 0), "%dh", (int) end / 3600);
	end %= 3600;
    }
    if (end >= 60) {
	sprintf(strchr(buf, 0), "%dm", (int) end / 60);
	end %= 60;
    }
    sprintf(strchr(buf, 0), "%ds)", (int) end);
    strncpy(linebuf + (wins.ws_col - strlen(buf)) / 2, buf, strlen(buf));
    fprintf(ttyFILE, "%s\r", linebuf);
    fflush(ttyFILE);
}

char *status_code(int code)
{
    static char msg[] = "Unknown status code 0xXX";

    switch (code) {
    case 0x00:
	return "GOOD";
    case 0x01:
	return "CHECK CONDITION";
    case 0x02:
	return "CONDITION GOOD";
    case 0x03:
	return "BUSY";
    case 0x08:
	return "INTERMEDIATE GOOD";
    case 0x0a:
	return "INTERMEDIATE CONDITION GOOD";
    case 0x0c:
	return "RESERVATION CONFLICT";
    case 0x11:
	return "COMMAND TERMINATED";
    case 0x14:
	return "QUEUE FULL";
    }
    sprintf(strchr(msg, 'x') + 1, "%02x", code & 255);
    return msg;
}

void sense_err(int status, unsigned char *buffer)
{
    unsigned char errbyte;

    static char *sense[16] =
    {
	"NO SENSE (generic error)",
	"RECOVERED ERROR", "NOT READY", "MEDIUM ERROR", "HARDWARE ERROR",
      "ILLEGAL REQUEST", "UNIT ATTENTION", "DATA PROTECT", "BLANK CHECK",
	"VENDOR SPECIFIC", "COPY ABORTED", "ABORTED COMMAND", "EQUAL",
	"VOLUME OVERFLOW", "MISCOMPARE", "RESERVED"
    };
    static char *driver_err[8] =
    {
	"Busy",
	"Soft error",
	"Media error",
	"Generic error",
	"Timed out",
	"Invalid request",
	"Hard error",
	"Additional sense information available"
    };
    static char *host_err[9] =
    {
	"Could not connect",
	"SCSI bus busy",
	"Device connect timed out",
	"Bad target id",
	"Command aborted",
	"SCSI bus parity error",
	"Internal error",
	"Adapter/target was reset",
	"Unexpected interrupt request"
    };

    status &= 0x0FFF00FF;
    if (!status)
	return;

    fprintf(stderr, "scsiformat: SCSI command failed, detailed analysis:\n\n");
    errbyte = status >> 16;
    if (errbyte) {
	if (errbyte <= 9)
	    fprintf(stderr, "Host adapter signalled: %s\n", host_err[errbyte - 1]);
	else
	    fprintf(stderr, "Host adapter signalled unknown error code: 0x%02x\n", errbyte);
    }
    errbyte = status >> 24;
    if (errbyte) {
	if (errbyte <= 8)
	    fprintf(stderr, "Device driver detected: %s\n", driver_err[errbyte - 1]);
	else
	    fprintf(stderr, "Device driver signalled unknown status code: 0x%02x\n", errbyte);
    }
    errbyte = status & 255;
    if (errbyte)
	fprintf(stderr, "Target device %s reported: %s\n", devicename, status_code(errbyte));
    if (((status & 0x0F000000) == 0x8000000) && ((buffer[0] & ~1) == 0x70)) {
	errbyte = buffer[2] & 0x0f;
	fprintf(stderr, "%s returned sense key %s.\n", devicename, sense[errbyte]);

	switch (errbyte) {
	case 0x05:		/* ILLEGAL_REQUEST */
	    fputs("\n"
		  "It might well be that the target does not support some of the\n"
		  "options you requested. Please try to use a simpler command.\n"
		  ,stderr);
	    fputs(par_xinterface ?
		  "Push <Simple Format Cmd> to reset the parameters to those which the device MUST\n"
		  "support.\n"
		  "The device really only has to support this and maybe <Erase Grown Defect List>\n"
		  "and some Interleave factor. It might also not support <Query progress from\n"
		  "target>.\n"
		  :
		  "Use -b<n> to enforce blocking mode. In addition the target only might have to\n"
		  "support -e and -i<n> (maybe -i0 only). All other options are really exotic and\n"
		  "not mandatory for the device.\n"
		  ,stderr);
	    break;
	case 0x07:		/* DATA_PROTECT */
	    fputs("\nMaybe the media is write protected?\n", stderr);
	    break;
	}
    }
    exit(1);
}

void format_unit()
{
    unsigned char buffer[2 * 65536 + 100];
    int hasdata = 0;
    unsigned char *cmd, *data, *end;
#ifndef DONT_FORMAT
    int status;
#endif

    catch_term();
    mk_tmpfile();

    memset(buffer, -1, sizeof(buffer));

    *(((int *) buffer) + 1) = 255;	/* length of output buffer */

    cmd = (unsigned char *) (((int *) buffer) + 2);

    cmd[0] = FORMAT_UNIT;
    cmd[1] = 0x00;		/* LUN / FmtData / CmpList / Defectformat */
    cmd[2] = 0x00;		/* (reserved / Vendor data) */
    cmd[3] = par_interleave >> 8;	/* (interleave hi) */
    cmd[4] = par_interleave & 255;	/* (interleave lo) */
    cmd[5] = 0x00;		/* (reserved) */

    data = cmd + 6;		/* format data block */

    data[0] = 0x00;		/* (reserved) */
    data[1] = 0x00;		/* flags */
    data[2] = 0x00;		/* (defect list length hi) */
    data[3] = 0x00;		/* (defect list length lo) */

    end = data + 4;
    cmd[1] |= 7 & par_deflistform;
    if (par_erasedefs)
	cmd[1] |= 0x08;
    if (par_initpattern[0] | par_initpattern[1]) {
	data[1] |= 0x08;
	par_fov = 1;
	memcpy(end, par_initpattern, par_patternlen);
	end += par_patternlen;
    }
    if (par_fov) {
	hasdata = 1;
	data[1] |= 0x80;
	if (par_disprim)
	    data[1] |= 0x40;
	if (par_discert)
	    data[1] |= 0x20;
	if (par_stop)
	    data[1] |= 0x10;
	if (par_dissav)
	    data[1] |= 0x04;
    }
    if (!par_blockmode) {
	data[1] |= 2;
	hasdata = 1;
    }
    if (par_defectlen) {
	hasdata = 1;
	memcpy(end, par_defects, par_defectlen);
	end += par_defectlen;
	data[2] = par_defectlen >> 8;
	data[3] = 255 & par_defectlen;
    }
    if (hasdata)
	cmd[1] |= 0x10;
    else
	end = data;
    *((int *) buffer) = end - cmd;	/* length of input data */
    if (par_debug) {
	printf("Sending FORMAT_UNIT command:\n");
	dump(stdout, ((int *) buffer) + 2, *((int *) buffer));
    }
    if (*((int *) buffer) > 4096) {
	fputs("scsiformat: FORMAT_UNIT command became larger than 4096 bytes.\n"
	   "Unfortunately this exceeds the capabilities of the current\n"
	   "implementation. Use a smaller defect list and/or a smaller\n"
	      "initialization pattern.\n", stderr);
	exit(2);
    }
#ifndef DONT_FORMAT
    status = send_scsi_cmd(fd, 6, buffer);
    if (status < 0) {
	argperr("scsiformat (sending FORMAT_UNIT to %s)", devicename);
	exit(1);
    }
    if (par_debug) {
	printf("Got SCSI status %02x and extended sense information:\n", status);
	dump(stdout, buffer + 8, 18);
    }
    sense_err(status, buffer + 8);
#endif
}

void usage(char *format,...)
{
    va_list params;

    if (format) {
	if (!par_xinterface)
	    fputs("Error: scsiformat - ", stderr);
	va_start(params, format);
	vfprintf(stderr, format, params);
	va_end(params);
	fputs("\n", stderr);
	if (!par_xinterface)
	    fputs("Enter 'scsiformat -H' for help.\n", stderr);
	exit(2);
    }
    fputs("Usage: scsiformat [-options] <device>\n\n"
	  "   Supported options are:\n"
	  "    a) Controlling a/synchronous operation\n"
	  "	-b<n>	block during the format operation. This makes any display of\n"
	  "		real progress indicators impossible. However, cheesy SCSI\n"
	  "		devices	will need it. Scsiformat assumes that the operation\n"
	  "		will need about n seconds and provides some progress indication\n"
	  "		according to that. -b0 does not print any process indication.\n"
	  "	-T	just test for a running format command and output statistics.\n"
	  "		A file /var/run/scsiformat.* is used to hold the starting time of\n"
	  "		the format operation. If formatting completes, this file is\n"
	  "		removed by scsiformat. The exit state is true as long as\n"
	  "		the format operation is still in progress.\n"
	  "	-t<n>\tcheck progress every <n> seconds (default is 5)\n"
	  "		-t0 makes scsiformat return without displaying progress.\n"
	  "\n"
	  "     b) Interleave factor\n"
	  "	-i<n>\tsets the sector interleave to be used.\n"
	  "		Usually you SHOULD stick with the default -i0 which selects a\n"
	  "		vendor specific default.\n"
	  "\n"
	  "     c) Initialization pattern\n"
    "	-I<hex>	the string given in hexcharacters is used to init all\n"
	  "		blocks on the device. By default the target may or may not init\n"
	  "		all data sectors.\n"
	  "	-L	First four bytes of each logical block are set to the block #.\n"
	  "	-P	First four bytes of each physical block are set to the logical\n"
	  "		block # it occurs in.\n"
	  "\n"
	  "     c) Defect management\n"
	  "	-e	Erase the grown defect list.\n"
	  "	-p	Ignore primary defectlist.\n"
	  "	-c	Do not perform a media surface certification.\n"
    "	-s	Stop when unable to access primary or grown defects.\n"
	  "	-S	Erase MODE SELECT settings stored in NVRAM.\n"
     "	-dlist	A comma seperated list of blocks to mark as defect.\n"
    "	-Dlist  A comma seperated list of expressions of the from C:H:S\n"
	  "		specifing defects at Cylinder:Head:Sector\n"
    "	-Blist  A comma seperated list of expressions of the from C:H:B\n"
     "		specifing defects at Cylinder:Head:Bytes from Index\n"
	  "	You can specify more than one of -d/-D/-B but may not mix types!\n"
	  "\n"
	  "    d) Simple partioning\n"
	  "	-f<arg>	perform simple partioning. -fdos sets up begin and start of the\n"
	  "		partition on cylinder boundaries. -ftight does use as much of\n"
	  "		the disk as possible. If you do not specify -f, scsiformat will\n"
	  "		not initialize the partition table at all. As it has to tell the\n"
	  "		kernel that the disk was reformatted and the kernel will try to\n"
	  "		to read the partition table, you are like to get some kernel\n"
	  "		warnings then.\n"
	  "	-G<h>x<s> set the disk geometry (Heads x Sectors) as DOS will see it\n"
	  "		for use in the partition table. If you don't specify it,\n"
	  "		scsiformat will ask the kernel what it thinks DOS will get from\n"
	  "		the adapters BIOS. This call might fail or return bogus data\n"
	  "		though. A wrong setting will not affect linux, but other OS's\n"
	  "		and esp. DOS and the BIOS (for booting).\n"
	  "	-y<type> set the type for the partion to set. <type> is a two digit\n"
	  "		hex number. See fdisk(8), command t for a list. Defaults to\n"
	  "		83 (Linux native).\n"
	  "	-M<sz>	Create a primary partition of maximal size <sz> MB. When <sz>\n"
	  "		is 0, no partion is created, and thus the partition table\n"
	  "		simply initialized to be valid (but empty). If the size <sz>\n"
	  "		exceeds the disk capacity, a partition as large as possible is\n"
	  "		made. Defaults to 99999.\n"
	  "     e) Miscanellous\n"
	  "	-H	print this help information.\n"
	  "	-v	print version information.\n"
	  "	-F<arg>	forced operation, do not ask prior to format.\n"
	  "		<arg> must be \"Ene Mene Meck, und Du bist weg!\" with proper\n"
	  "		spaces and capitalization. (this is a german child rhyme\n"
	  "		kissing	someone goodbye...)\n"
	  "	-V	print some debugging information.\n"
	  "	-X	all output is printed in numerics, useful for GUI interfaces.\n"
	  "	-o	The settings of the flags -c, -p, -s, -S, -I, -L, -P are obeyed.\n"
	  "		If you specify one of these, -o is silently added. Without\n"
	  "		-o some factory default is used. Specfiying just -o will\n"
	  "		allow you to not use any of these options which might not be\n"
	  "		the default choosen by the device otherwise.\n"
	  ,stdout);
    exit(0);
}

unsigned checked_int(unsigned limit)
{
    char *ptr = optarg;
    unsigned long tmp = 0;

    while (*ptr) {
	if (!isdigit(*ptr)) {
	    usage("Error: scsiformat - illegal character '%c' (ascii %02xh)\n"
		  "\tin integer parameter for option -%c.",
		  (int) *ptr, (int) *ptr, optopt);
	}
	tmp *= 10;
	tmp += *ptr - '0';
	if (tmp > (unsigned long) limit) {
	    usage("integer parameter for option -%c is out of range.\n"
		  "Allowed range is 0 to %u.",
		  optopt, limit);
	}
	ptr++;
    }
    return (unsigned) tmp;
}

char *hunt_tmpfname(void)
{
    char *ptr, *buff, name1[40], name2[40];
    DIR *dir;
    FILE *fd;
    struct dirent *dptr;
    int dummy;

    buff = alloca(strlen(tmpfname) + NAME_MAX);
    strcpy(buff, tmpfname);
    ptr = strrchr(buff, '/') + 1;
    *ptr = 0;
    dir = opendir(buff);

    if (!dir)
	return NULL;
    while ((dptr = readdir(dir))) {
	if (strncmp(dptr->d_name, "scsiformat.", strlen("scsiformat.")))
	    continue;
	strcpy(ptr, dptr->d_name);
	if (strlen(buff) > strlen(tmpfname))
	    continue;
	fd = fopen(buff, "r");
	if (fd) {
	    dummy = fscanf(fd, "%d%d%d %35s %35s\n", &dummy, &dummy, &dummy,
			   name1, name2);
	    fclose(fd);
	    if ((dummy == 5) && (!strcmp(name1, devicename) ||
				 !strcmp(name2, devicename))) {
		strcpy(tmpfname, buff);
		closedir(dir);
		return tmpfname;
	    }
	}
    }
    closedir(dir);
    return NULL;
}

void mk_tmpfile(void)
{
    FILE *fh;
    time_t curtime;
    int flag, i, off;
    char buffer[40], *eol = "\n";

    fh = fopen(tmpfname, "w");
    if (!fh) {
	argperr("Warning, scsiformat (Opening %s)", tmpfname);
	errno = 0;
	return;
    }
    time(&curtime);

    fprintf(fh,
	    "%u %u %u %s %s\n"
	    "This file was written by: %s\n\n"
	    "You can securely remove this file which is used to record the start of a\n"
	    "currently running SCSI FORMAT_UNIT command. The worst thing that will happen\n"
	    "is that some tools may not properly estimate completion time and other stats.\n"
	    "The creation time of this file is the starting time. Usually it should be\n"
	    "removed automatically when no longer needed.\n\n"
    "Parameters given to scsiformat (just for informational purposes):\n",
	    (unsigned) par_blockmode, par_checkint, par_estimate,
	    devicename, givendevice,
	    VERSION);
    fprintf(fh, "Device name entered:  %s\n", devicename);
    fprintf(fh, "Device name used:     %s\n", devicename);
    fprintf(fh, "Starting time:        %s", ctime(&curtime));
    fprintf(fh, "Check interval:       %u\n", par_checkint);
    if (par_blockmode)
	fprintf(fh, "Blocking, time estim: %u\n", par_estimate);
    fprintf(fh, "\nInterleave factor:    %u\n", par_interleave);
    eol = "\n";
    if (par_initpattern[0] >> 6) {
	fprintf(fh, "\nWrite logical block # in each %s block\n",
		par_initpattern[0] & 0x40 ? "logical" : "physical");
	eol = "";
    }
    if (par_initpattern[2] == 1) {
	fprintf(fh, "%sData init pattern:\n", eol);
	dump(fh, par_initpattern + 4, par_patternlen - 4);
	eol = "\n";
    }
    if (par_erasedefs) {
	fprintf(fh, "%sErase grown defects.\n", eol);
	eol = "";
    }
    if (par_disprim) {
	fprintf(fh, "%sIgnore primary defects.\n", eol);
	eol = "";
    }
    if (par_discert) {
	fprintf(fh, "%sNo media certification.\n", eol);
	eol = "";
    }
    if (par_stop) {
	fprintf(fh, "%sStop when unable to access defect lists.\n", eol);
	eol = "";
    }
    if (par_dissav) {
	fprintf(fh, "%sErase MODE SELECT NVRAM.\n", eol);
	eol = "";
    }
    if (par_fov) {
	fprintf(fh, "%sDo not use vendor default defect management.\n", eol);
	eol = "";
    }
    if (par_defectlen) {
	flag = 0;
	if (!par_deflistform) {
	    fprintf(fh, "\nUser supplied defect logical blocks:\n");
	    for (i = off = 0; off < par_defectlen; i++, off += 4) {
		fprintf(fh, " %9u", ntohl(par_defects[i]));
		if ((i & 7) == 7) {
		    fprintf(fh, "\n");
		    flag = 1;
		} else
		    flag = 0;
	    }
	} else {
	    fprintf(fh, "\nUser supplied defects in Cylinder:Head:%s format:\n",
	     par_deflistform == 5 ? "PhysicalSector" : "BytesFromIndex");
	    for (i = off = 0; off < par_defectlen; i += 2, off += 8) {
		sprintf(buffer, " %u:%u:%d", ntohl(par_defects[i]) >> 8,
			ntohl(par_defects[i]) & 255, (int) ntohl(par_defects[i + 1]));
		fprintf(fh, "%20s", buffer);
		if ((i & 3) == 3) {
		    fprintf(fh, "\n");
		    flag = 1;
		} else
		    flag = 0;
	    }
	}
	if (!flag)
	    fprintf(fh, "\n");
    }
    flag = 0;
    if (ferror(fh))
	flag = 1;
    if (fclose(fh))
	flag = 1;
    if (flag) {
	argperr("Warning, scsiformat (Writing to %s)", tmpfname);
	errno = 0;
	return;
    }
}

void rm_tmpfile()
{
    if (unlink(tmpfname) && (errno != ENOENT)) {
	argperr("Warning, scsiformat (Removing %s)", tmpfname);
	errno = 0;
    }
}

void do_checkmode(int block)
{
    unsigned a, b, c;
    int head = 0;
    struct stat stbuf;
    time_t end = 0, current;
    FILE *fd;

  restart:
    par_blockmode = 0;

    fd = fopen(tmpfname, "r");
    if (fd) {
	if (3 == fscanf(fd, "%u%u%u", &a, &b, &c)) {
	    if (!(a & ~1)) {
		par_blockmode = a;
		par_estimate = c;
	    }
	}
	if (!fstat(fileno(fd), &stbuf)) {
	    start = stbuf.st_mtime;
	    end = start + par_estimate;
	    if (time(NULL) - start > (48 * 3600)) {
		fputs("Status file older than two days: Cleaning up.\n", ttyFILE);
		par_blockmode = 0;
	    }
	} else {
	    par_blockmode = 0;
	}
	fclose(fd);
    }
    if (par_blockmode) {
	if (!par_checkint && !par_xinterface)
	    exit(1);

	if (!head && !par_xinterface)
	    mk_head();
	head = 1;
	while (!stat(tmpfname, &stbuf)) {
	    if (start != stbuf.st_mtime)
		goto restart;	/* wow, formatting restarted while we slept */
	    current = time(NULL) - start;
	    if (par_xinterface) {
		if (par_estimate)
		    Xestimate(start, end, (current * 65535 + par_estimate / 2) / par_estimate);
		else
		    Xestimate(start, 0, -1);
		exit(1);
	    }
	    if (par_estimate) {
		mk_statusbar(start, end,
		    (current * 65535 + par_estimate / 2) / par_estimate);
	    } else {
		mk_statusbar(start, end, -1);
	    }
	    sleep(par_checkint);
	}
	if (errno == ENOENT) {
	    if (par_checkint)
		exit_time();
	    exit(0);
	}
	argperr("scsiformat (accessing %s)", tmpfname);
	exit(1);
    } else if (block) {
	int percentage;

	percentage = request_sense();

#ifndef BLOCKING_ONLY
	if (par_xinterface || !par_checkint) {
	    if (!par_xinterface) {
		/* check for status */
		if (percentage >= 0)
		    exit(1);
		else if (!stat(tmpfname, &stbuf))
		    exit(1);
		else
		    exit(0);
	    }
	    if (percentage >= 0) {
		end = start + ((time(NULL) - start) * percentage + 32768) / 65535;
		Xestimate(start, end, percentage);
		exit(1);
	    } else if (!stat(tmpfname, &stbuf)) {
		Xestimate(start, time(NULL), 65535);
		exit(1);
	    } else {
		exit(0);
	    }
	}
	mk_head();

	while ((percentage = request_sense()) >= 0) {
	    end = start + ((time(NULL) - start) * percentage + 32768) / 65535;
	    mk_statusbar(start, end, percentage);
	    sleep(par_checkint);
	}
	exit_time();
	fprintf(ttyFILE, "Setting up partition tables.\n");
	fflush(ttyFILE);
	while (!stat(tmpfname, &stbuf))
	    sleep(par_checkint);
	exit(0);
#endif
    }
}

void term_handler(int signal)
{
    rm_tmpfile();
    if (ioctl(fd, BLKRRPART, NULL)) {
	argperr("scsiformat (revalidating scsi disk %s)", devicename);
	exit(1);
    }
    raise(signal);
}

void catch_term(void)
{
    struct sigaction siga;
    int i;
    unsigned char sig2catch[] =
    {SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGFPE,
     SIGSEGV, SIGPIPE, SIGALRM, SIGTERM, SIGUSR1, SIGUSR2, SIGTRAP,
     SIGIOT, SIGBUS,
#ifdef SIGSTKFLT
     SIGSTKFLT,
#endif
     SIGIO, SIGXCPU, SIGXFSZ, SIGVTALRM,
     SIGPROF, SIGPWR};

    siga.sa_handler = term_handler;
    siga.sa_flags = SA_ONESHOT | SA_NOMASK;
    sigemptyset(&siga.sa_mask);

    for (i = 0; i < sizeof(sig2catch); i++) {
	if (sigaction((int) sig2catch[i], &siga, NULL)) {
	    argperr("scsiformat (catching signal %d, %s)",
		    (int) sig2catch[i], strsignal((int) sig2catch[i]));
	    exit(1);
	}
    }
}

void exit_time(void)
{
    int i;

    start = time(NULL) - start;
    for (i = 0; i < wins.ws_col; i++)
	fputc(' ', ttyFILE);
    fprintf(ttyFILE, "\r\aFormatting needed ");
    i = start;
    if (i >= 3600) {
	fprintf(ttyFILE, "%dh", i / 3600);
	i %= 3600;
    }
    if (i >= 60) {
	fprintf(ttyFILE, "%dm", i / 60);
	i %= 60;
    }
    fprintf(ttyFILE, "%ds or %d seconds this time.\n", i, (int) start);
    fflush(ttyFILE);
}

void child_handler(int signal)
{
    int status;

    if (child != wait(&status))
	return;
    if (WIFEXITED(status)) {
	status = WEXITSTATUS(status);
	if (status != 0)
	    exit(status);
    } else if (WIFSIGNALED(status)) {
	fprintf(stderr, "Formatting child process killed by signal %d, %s.\n",
		WTERMSIG(status), strsignal(WTERMSIG(status)));
	exit(1);
    } else
	return;
    exit_time();
    exit(0);
}

void do_format()
{
    struct sigaction siga;

    if (ioctl(fd, BLKFLSBUF, NULL)) {
	argperr("scsiformat (flushing disk buffers of %s)", devicename);
	exit(1);
    }
    if (!par_blockmode) {
#ifdef BLOCKING_ONLY
	fputs("Non blocking yet unsupported in this scsiformat binary!\n", stderr);
	exit(2);
#else
	mk_tmpfile();
	format_unit();
	child = fork();
	if (child < 0) {
	    perror("scsiformat (fork()ing format process)");
	    exit(1);
	} else if (child) {	/* parent */
	    int percentage;
	    time_t end;
	    struct stat stbuf;

	    if (!par_xinterface && par_checkint) {
		mk_head();
		while ((percentage = request_sense()) >= 0) {
		    end = start + ((time(NULL) - start) * percentage + 32768) / 65535;
		    mk_statusbar(start, end, percentage);
		    sleep(par_checkint);
		}
		exit_time();
		fprintf(ttyFILE, "Setting up partition tables.\n");
		fflush(ttyFILE);
		while (!stat(tmpfname, &stbuf))
		    sleep(par_checkint);
		exit(0);
	    }
	} else {		/* child */
	    catch_term();
	    if (!par_checkint)
		par_checkint = 1;
	    while (request_sense() >= 0)
		sleep(par_checkint);
	    init_partition();
	    rm_tmpfile();
	}
	exit(0);
#endif
    }
    if (par_estimate || par_xinterface) {
	/* Blocking, but provide progress indication */
	time_t end, current;

	start = time(NULL);
	end = time(NULL) + par_estimate;

	siga.sa_handler = child_handler;
	siga.sa_flags = SA_NOCLDSTOP;
	sigemptyset(&siga.sa_mask);

	if (sigaction(SIGCHLD, &siga, NULL)) {
	    perror("scsiformat (catching SIGCHLD)");
	    exit(1);
	}
	child = fork();
	if (child < 0) {
	    perror("scsiformat (fork()ing format process)");
	    exit(1);
	} else if (child) {	/* this is the parent */
	    /* allow the child to fail right away in case of problems: */
	    sleep(2);
	    if (!par_checkint || par_xinterface) {
		/* Ok, no problems til now. Assume all is ok. */
		exit(0);
	    }
	    sigemptyset(&siga.sa_mask);
	    sigaddset(&siga.sa_mask, SIGCHLD);
	    sigprocmask(SIG_BLOCK, &siga.sa_mask, NULL);
	    mk_head();
	    for (;;) {
		current = time(NULL) - start;
		sigprocmask(SIG_BLOCK, &siga.sa_mask, NULL);
		mk_statusbar(start, end,
		    (current * 65535 + par_estimate / 2) / par_estimate);
		sigprocmask(SIG_UNBLOCK, &siga.sa_mask, NULL);
		sleep(par_checkint);
	    }
	} else {		/* child */
	    catch_term();
	    mk_tmpfile();
	    format_unit();
	    init_partition();
	    rm_tmpfile();
	    exit(0);
	}
    }
    mk_tmpfile();
    format_unit();
    init_partition();
    rm_tmpfile();
}

unsigned char ghexdigit(int c)
{
    switch (c) {
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
	return c - '0';
    case 'A':
    case 'B':
    case 'C':
    case 'D':
    case 'E':
    case 'F':
	return c - 'A' + 10;
    case 'a':
    case 'b':
    case 'c':
    case 'd':
    case 'e':
    case 'f':
	return c - 'A' + 10;
    }
    usage("illegal non hex character '%c' (ascii %02x)\n"
	  "in initialization pattern given to '-p' flag.",
	  c, c);
    return 0;
}

void parse_pattern()
{
    char *ptr = optarg;
    int len;

    len = strlen(ptr);
    if (len > 2 * 65536)
	usage("The initilialization pattern must have less than 65536 bytes.");
    par_initpattern[4] = 0;
    par_patternlen = 0;
    if (len & 1)
	goto skipfirst;
    while (*ptr) {
	par_initpattern[4 + par_patternlen] = ghexdigit((int) *ptr++) << 4;
      skipfirst:
	par_initpattern[4 + par_patternlen] |= ghexdigit((int) *ptr++);
	par_patternlen++;
    }
    par_initpattern[1] = 1;
    par_initpattern[2] = par_patternlen >> 8;
    par_initpattern[3] = 255 & par_patternlen;
    par_patternlen += 4;
}

void cano_device(void)
{
    long tmpunid[2];
    static char newdev[] = "/dev/sda";
    int newfd;


    if (ioctl(fd, 0x5382 /* SCSI_IOCTL_GET_IDLUN */ , scsiunid))
	usage("%s is no SCSI device.", devicename);
    for (newdev[7] = 'a'; newdev[7] <= 'z'; newdev[7]++) {
	newfd = open(newdev, O_RDWR);
	if (newfd >= 0) {
	    if ((!ioctl(newfd, 0x5382 /* SCSI_IOCTL_GET_IDLUN */ , tmpunid)) &&
	    (scsiunid[0] == tmpunid[0]) && (scsiunid[1] == tmpunid[1])) {
		close(fd);
		fd = newfd;
		devicename = newdev;
		return;
	    }
	    close(newfd);
	}
	errno = 0;
    }
}

int remove_swaps(void)
{
    char *partdev;
    int i, o, flag = 0;

    o = strlen(devicename);
    partdev = alloca(o + 5);
    strcpy(partdev, devicename);

    for (i = 1; i < 32; i++) {
	sprintf(partdev + o, "%d", i);
	if (!swapoff(partdev)) {
	    fprintf(stderr, "Warning, turned swap off on %s!\n", partdev);
	} else if ((errno != ENOENT) && (errno != EINVAL) && (errno != ENXIO)) {
	    fprintf(stderr, "Warning, unable to turn swap off on %s!\n", partdev);
	    flag = 1;
	}
	errno = 0;
    }
    partdev[o + 1] = 0;
    for (partdev[o] = 'a'; partdev[o] <= 'z'; partdev[o]++) {
	if (!swapoff(partdev)) {
	    fprintf(stderr, "Warning, turned swap off on %s!\n", partdev);
	} else if ((errno != ENOENT) && (errno != EINVAL) && (errno != ENXIO)) {
	    fprintf(stderr, "Warning, unable to turn swap off on %s!\n", partdev);
	    flag = 1;
	}
	errno = 0;
    }
    return flag;
}

/* Don't trust memcmp to work with unsigned chars */
int mycmp(unsigned char *a, unsigned char *b, int n)
{
    while (n--)
	if (*a++ != *b++)
	    return ((int) *(a - 1)) - ((int) *(b - 1));
    return 0;
}

void insert_defect(int size, unsigned long a, unsigned long b)
{
    unsigned char newdata[8], *tablow, *tabhigh, *new;

    *(unsigned long *) newdata = htonl(a);
    *(unsigned long *) (newdata + 4) = htonl(b);

    if (!par_defectlen) {
	memcpy(par_defects, newdata, size);
	par_defectlen += size;
	return;
    }
    tablow = (void *) par_defects;
    tabhigh = tablow + par_defectlen - size;
    while ((tablow + size) < tabhigh) {
	new = tablow + size * ((tabhigh - tablow) / (size << 1));
	if (mycmp(new, newdata, size) < 0)
	    tablow = new;
	else if (mycmp(new, newdata, size) > 0)
	    tabhigh = new;
	else
	    return;		/* entry was found */
    }
    if (!mycmp(tablow, newdata, size))
	return;
    if (!mycmp(tabhigh, newdata, size))
	return;
    /* now we got a new entry, we might have to add it behind tabhigh or right before */
    if (mycmp(tabhigh, newdata, size) < 0)
	tabhigh += size;
    /* Ok, right behind then. */
    new = ((unsigned char *) (void *) par_defects) + par_defectlen;
    if (tabhigh != new)
	memmove(tabhigh + size, tabhigh, new - tabhigh);
    memcpy(tabhigh, newdata, size);
    par_defectlen += size;
}

void add_defect(char type)
{
    char *arg = optarg;
    char format = 0;
    unsigned long cyl, head, sec;

    if (type == 'D')
	format = 5;
    else if (type == 'B')
	format = 4;

    if (par_defectlen && (format != par_deflistform))
	usage("Specify only one kind of -d, -D, -B options.");
    par_deflistform = format;

    while (*arg) {
	if (isspace(*arg)) {
	    arg++;
	    continue;
	}
	if (!isdigit(*arg))
	    goto wrongchar;
	if (!format) {
	    insert_defect(4, strtoul(arg, &arg, 0), 0);
	    if (par_defectlen > 65536)
		usage("A maximum of 16384 -d defects is allowed.");
	    while (isspace(*arg))
		arg++;
	    if (*arg == ',')
		arg++;
	    else if (*arg) {
	      wrongchar:
		usage("Illegal or unexpected character '%c' (0x%02x) in defect list.",
		      *arg, *arg);
	    }
	} else {
	    cyl = strtoul(arg, &arg, 0);
	    while (isspace(*arg))
		arg++;
	    if (*arg != ':') {
	      needcol:
		usage("Colon expected in defect list parameter.");
	    }
	    head = strtoul(arg + 1, &arg, 0);
	    while (isspace(*arg))
		arg++;
	    if (*arg != ':')
		goto needcol;
	    sec = strtoul(arg + 1, &arg, 0);
	    while (isspace(*arg))
		arg++;
	    if (*arg == ',')
		arg++;
	    else if (*arg)
		goto wrongchar;
	    if (cyl & 0xFF000000UL)
		usage("Cylinders must be in range 0..%d.", 0xFFFFFF);
	    if (head & 0xFFFFFF00UL)
		usage("Heads must be in range 0..%d.", 0xFF);
	    insert_defect(8, (cyl << 8) | head, sec);
	    if (par_defectlen > 65536)
		usage("A maximum of 8192 -d defects is allowed.");
	}
    }
}

void prompt_user(void)
{
    FILE *ttyIN = NULL;
    int a, b, c;
    struct termios ttystat, ttystat_org;

    if (!isatty(ttyfd) || !(ttyIN = fdopen(ttyfd, "r")))
	usage("Unable to reach the user and prompt for confirmation.\n");

    tcgetattr(ttyfd, &ttystat_org);
    ttystat = ttystat_org;
    ttystat.c_iflag &= INLCR | IGNCR;
    ttystat.c_iflag |= ICRNL;
    ttystat.c_lflag |= ICANON | ISIG | ECHO;
    tcsetattr(ttyfd, TCSANOW, &ttystat);

    srand((unsigned) time(NULL));
    a = 1 + (int) (24.0 * rand() / (RAND_MAX + 1.0));
    b = 1 + (int) (24.0 * rand() / (RAND_MAX + 1.0));
    fprintf(ttyFILE, "\aWarning! About to lowlevel format / partition %s!\n\n"
	    "This will erase all data on %s, on any partition, be it linux or not.\n"
	    "To ensure that you are really awake and as a confirmation, please enter\n"
	    "the solution for this simple multiplication:\n\n"
	    "%d * %d = ",
	    devicename, devicename, a, b);
    fflush(ttyFILE);
    fscanf(ttyIN, "%d", &c);
    tcsetattr(ttyfd, TCSANOW, &ttystat_org);
    if (a * b != c) {
	fprintf(ttyFILE, "\nWrong. You lost, but your data won.\n");
	exit(2);
    }
    /* We cannot close ttyIN, that would also close ttyfd !!! */
    fprintf(ttyFILE, "\nOk, you asked for it...\n");
    fflush(ttyFILE);
}

int main(int argc, char *argv[])
{
    char c;
    int flag, par;
    FILE *mntfd;
    struct mntent *mnt;

    if (argc < 2)
	usage("Too few arguments.");

    par_initpattern[0] = par_initpattern[1] = par_initpattern[3] = par_initpattern[3] = 0;

    while ((c = getopt(argc, argv, "b:t:I:i:vVXTeopicsSd:D:B:PLHF:M:y:f:G:")) != EOF) {
	switch (c) {
	case 'M':
	    par_partsize = checked_int(99999);
	    break;
	case 'f':
	    if (strcasecmp(optarg, "dos") == 0) {
		par_dopart = DOSPART;
	    } else if (strcasecmp(optarg, "tight") == 0) {
		par_dopart = TIGHTPART;
	    } else {
		usage("Illegal argument to -f. Must be `dos' or `tight'!");
	    }
	    break;
	case 'y':
	    sscanf(optarg, "%x%n", &par, &flag);
	    if ((flag != strlen(optarg)) || (strlen(optarg) > 2) || !strlen(optarg))
		usage("Illegal argument to -y. Must be `dos' or `tight'!");
	    part_type = par;
	    break;
	case 'G':
	    if ((3 != sscanf(optarg, "%ux%u%n", &par_heads, &par_sectors, &flag)) ||
		(flag != strlen(optarg)))
		usage("Illegal argument to -G.");
	    if ((par_heads | par_sectors) & 0xFF00)
		usage("Option -G: Heads and sectors must be in range 0..255 (at least!)!");
	    par_geom = 1;
	    break;
	case 'F':
	    if (strcmp(optarg, "Ene Mene Meck, und Du bist weg!"))
		usage("Illegal argument to -F.");
	    par_force = 1;
	    break;
	case 't':
	    par_checkint = checked_int(3600);
	    break;
	case 'i':
	    par_interleave = checked_int(65535);
	    break;
	case 'v':
	    fprintf(stderr, VERSION "\n");
	    exit(0);
	    break;
	case 'V':
	    par_debug = 1;
	    break;
	case 'X':
	    par_xinterface = 1;
	    break;
	case 'T':
	    par_checkmode = 1;
	    break;
	case 'b':
	    par_blockmode = 1;
	    par_estimate = checked_int(0x7FFFFFFF);
	    break;
	case 'I':
	    parse_pattern();
	    break;
	case 'L':
	    par_initpattern[0] = 0x40;
	    break;
	case 'P':
	    par_initpattern[0] = 0x80;
	    break;
	case 'e':
	    par_erasedefs = 1;
	    break;
	case 'o':
	    par_fov = 1;
	    break;
	case 'p':
	    par_disprim = 1;
	    par_fov = 1;
	    break;
	case 'c':
	    par_discert = 1;
	    par_fov = 1;
	    break;
	case 's':
	    par_discert = 1;
	    par_fov = 1;
	    break;
	case 'S':
	    par_dissav = 1;
	    par_fov = 1;
	    break;
	case 'd':
	case 'D':
	case 'B':
	    add_defect(c);
	    break;
	default:
	    usage("Unknown option '-%c' (0x%02x).", c, c);
	case 'H':
	    usage(NULL);
	};
    };

    if (optind < argc - 1)
	usage("Specify only one device to format.");
    if (optind > argc - 1)
	usage("No device to format given.");
    givendevice = devicename = argv[optind];

#if 0
    if (!par_checkmode) {
	fputs("forced to sdd!!\n", stderr);
	devicename = "/dev/sdd";
    }
#endif

    wins.ws_col = 80;
    if (!isatty(ttyfd)) {
	flag = open("/dev/tty", O_RDWR);
	if (flag >= 0) {
	    ttyFILE = fdopen(ttyfd, "w+");
	    if (ttyFILE)
		ttyfd = flag;
	}
    }
    ioctl(ttyfd, TIOCGWINSZ, &wins);
    if (ttyfd == 2)
	ttyFILE = stderr;

    if (wins.ws_col < 40)
	wins.ws_col = 40;
    wins.ws_col--;
    linebuf = alloca(wins.ws_col + 10);

    /* The device might be in blocking mode, before trying to open it,
       we look for a status file */
    if (par_checkmode) {
	if (hunt_tmpfname())
	    do_checkmode(0);	/* hmm we find a file looking good.. use it when its blocking */
    }
    /* generic scsi must be opened R/W, others should be opened read_only */
    /* alas, we need to WRITE the partition table later, and as we are */
    /* going to erase all of it, we really should have write permission */

    fd = open(devicename, O_RDWR);
    if (fd < 0) {
	argperr("scsiformat (opening %s)", devicename);
	exit(1);
    }
    cano_device();
    mntfd = setmntent(MOUNTED, "r");
    if (!mntfd) {
	argperr("scsiformat (opening %s)", MOUNTED);
	exit(1);
    }
    flag = 0;
    while ((mnt = getmntent(mntfd))) {
	if (!strncmp(devicename, mnt->mnt_fsname, strlen(devicename))) {
	    fprintf(stderr, "%s is still mounted on %s!\n", mnt->mnt_fsname,
		    mnt->mnt_dir);
	    flag = 1;
	}
    }
    if (flag)
	exit(1);
    endmntent(mntfd);
    if (remove_swaps())
	exit(1);

    sprintf(strchr(tmpfname, 'X'), "%02lx:%02lx:%02lx:%02lx:%08lx",
	    255 & (scsiunid[0] >> 24),
	    255 & (scsiunid[0] >> 16),
	    255 & scsiunid[0],
	    255 & (scsiunid[0] >> 8), scsiunid[1]);

    if (par_checkmode)
	do_checkmode(1);
    else {
	struct hd_geometry geom;

	if ((!par_geom) && (par_dopart != NOPART)) {
	    if (ioctl(fd, HDIO_GETGEO, &geom)) {
		argperr("scsiformat (failed getting disk geometry for %s, use -G)", devicename);
		exit(1);
	    }
	    par_heads = geom.heads;
	    par_sectors = geom.sectors;
	}
	if (!par_force)
	    prompt_user();
	do_format();
    }
    if (close(fd)) {
	argperr("scsiformat (closing %s)", devicename);
	exit(1);
    }
    return 0;
}

void flush_disk(int op)
{
    int retries = WAIT_DISK;

  retry:
    if (ioctl(fd, op, NULL)) {
	if ((errno == EBUSY) || (errno == EAGAIN)) {
	    retries--;
	    if (retries) {
		sleep(1);
		goto retry;
	    }
	}
	argperr(op == BLKRRPART ? "scsiformat (revalidating scsi disk %s)" :
		"scsiformat (flushing disk buffers of %s)", devicename);
	exit(1);
    }
}

void init_partition(void)
{
    unsigned char bootsector[SECTOR_SIZE];
    struct partition *part1 = (void *) (bootsector + 0x1be);
    unsigned short *flag = (void *) (bootsector + 0x1fe);
    unsigned long capacity;

    if (par_dopart == NOPART) {
	/* Note: As we did not touch the partition sector, the kernel with most likely */
	/* complain about an invalid partion. This is needed, however, to make the */
	/* kernel reread the size of the device */

	/* It is rumored that you have to do this twice: */
	flush_disk(BLKRRPART);
	flush_disk(BLKRRPART);
	return;
    }
    memset(bootsector, 0, sizeof(bootsector));
    *flag = 0xaa55;

    /* First write this empty table, to make the kernel happy */
    if (-1 == lseek(fd, 0, SEEK_SET)) {
      err_seek:
	argperr("scsiformat (seeking to bootsector of %s)", devicename);
	exit(1);
    }
    if (SECTOR_SIZE != write(fd, bootsector, SECTOR_SIZE)) {
      err_write:
	argperr("scsiformat (writing bootsector of %s)", devicename);
	exit(1);
    }
    sync();
    sleep(1);			/* sync() should have block anyhow */
    flush_disk(BLKFLSBUF);
    /* It is rumored that you have to do this twice: */
    flush_disk(BLKRRPART);
    flush_disk(BLKRRPART);
    sleep(1);
    if (!par_partsize)
	return;			/* Ok, if we shall not add a partition, this was it */
    /* Now we can securely query the size */
    if (ioctl(fd, BLKGETSIZE, &capacity)) {
	argperr("scsiformat (reading capacity of %s)", devicename);
	exit(1);
    }
    /* Now set up the partition: */
    par_partsize *= 2048;	/* amount of sectors */

    part1->boot_ind = 0;	/* not active */
    part1->sys_ind = part_type;
    if (par_dopart == DOSPART)
	part1->start_sect = par_heads * par_sectors;	/* start on a cylinder boundary */
    else
	part1->start_sect = 1;	/* With ext2 we could probably even use 0, but that will */
    /* not work with other fs types */
    /* and for brain dead DOS: */
    set_hsc(part1->head, part1->sector, part1->cyl, part1->start_sect);
    part1->nr_sects = par_partsize;

    if ((part1->start_sect + part1->nr_sects) >= capacity)
	part1->nr_sects = capacity - part1->start_sect - 1;

    /* For DOS it might be wise to round to a register boundary too: */
    if (par_dopart == DOSPART)
	part1->nr_sects -= (part1->nr_sects % (par_heads * par_sectors));

    set_hsc(part1->end_head, part1->end_sector, part1->end_cyl,
	    part1->start_sect + part1->nr_sects - 1);

    /* And move it to the disk, telling the kernel about it. */
    if (-1 == lseek(fd, 0, SEEK_SET))
	goto err_seek;
    if (SECTOR_SIZE != write(fd, bootsector, SECTOR_SIZE))
	goto err_write;
    sync();
    sleep(1);			/* sync() should have block anyhow */
    flush_disk(BLKFLSBUF);
    flush_disk(BLKRRPART);
    flush_disk(BLKRRPART);
    sleep(1);
}
