/* fdisk.c -- Partition table manipulator for Linux.
 *
 * Copyright (C) 1992  A. V. Le Blanc (LeBlanc@mcc.ac.uk)
 *
 * 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.
 *
 * Before Linux version 0.95c, this program requires a kernel patch.
 *
 * Modified, Tue Feb  2 18:46:49 1993, faith@cs.unc.edu to better support SCSI.
 * Modified, Sat Feb 27 18:11:27 1993, faith@cs.unc.edu: added extfs support.
 * Modified, Sat Mar  6 10:14:12 1993, faith@cs.unc.edu: added more comments.
 * Modified, Sat Mar  6 12:25:45 1993, faith@cs.unc.edu:
 *	Added patches from Michael Bischoff (i1041905@ws.rz.tu-bs.de
 *	or mbi@mo.math.nat.tu-bs.de) to fix the following problems:
 *	1) Incorrect mapping of head/sector/cylinder to absolute sector
 *	2) Odd sector count causes one sector to be lost
 * Modified, Sat Mar  6 12:25:52 1993, faith@cs.unc.edu: improved verification.
 * Modified, Sat Apr 17 15:00:00 1993, LeBlanc@mcc.ac.uk: add -s, fix -l.
 * Modified, Sat Apr 24 10:00:00 1993, LeBlanc@mcc.ac.uk: fix overlap bug.
 * Modified, Wed May  5 21:30:00 1993, LeBlanc@mcc.ac.uk: errors to stderr.
 *
 *
 */

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <setjmp.h>

#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/fs.h>

#define hex_val(c)	({ \
				char _c = (c); \
				isdigit(_c) ? _c - '0' : \
				tolower(_c) + 10 - 'a'; \
			})


/* Since I get no response from e-mail to LeBlanc@mcc.ac.uk, I\'m taking
 * over the version number.
 * Rik Faith, Sat Feb 27 18:14:49 1993 */
#define VERSION	"1.3"

#define DEFAULT_DEVICE	"/dev/hda"
#define ALTERNATE_DEVICE "/dev/sda"
#define LINE_LENGTH	80
#define MAXIMUM_PARTS	60
#define SECTOR_SIZE	512
#define PART_TABLE_FLAG	0xaa55
#define table_check(b)	((unsigned short *)((b) + 0x1fe))
#define offset(b, n)	((struct partition *)((b) + 0x1be + \
				(n) * sizeof(struct partition)))
#define sector(s)	((s) & 0x3f)
#define cylinder(s, c)	((c) | (((s) & 0xc0) << 2))

#if 0
#define calculate(h,s,c) ((cylinder(s,c) * sectors + sector(s) - 1) \
				* heads + (h))
#define t_head(t)	((t) % heads)
#define t_sector(t)	((sector((t) % sectors + 1)) \
				| (((t) / sectors) >> 2) & 0xc0)
#define t_cylinder(t)	((t) / sectors & 0xff)
#else
#define calculate(h,s,c) (sector(s)-1 + sectors * \
				((h) + heads * cylinder(s,c)))
#define set_hsc(h,s,c,sector) { \
				s = sector % sectors + 1; \
				sector /= sectors;	\
				h = sector % heads;	\
				sector /= heads;	\
				c = sector & 0xff;	\
				s |= (sector >> 2) & 0xc0; \
			}
#endif

#define cround(n)	(((n) + display_factor * unit_flag) / display_factor)
#define ACTIVE_FLAG	0x80
#define EXTENDED	5

#define LINUX_PARTITION	0x81
#define LINUX_SWAP	0x82
#define LINUX_EXT	0x83

enum failure {usage, unable_to_open, unable_to_read, unable_to_seek,
	unable_to_write, out_of_memory};

char	*disk_device = DEFAULT_DEVICE,	/* hda, unless specified */
	*line_ptr,			/* interactive input */
	line_buffer[LINE_LENGTH],
	changed[MAXIMUM_PARTS],		/* marks changed buffers */
	buffer[SECTOR_SIZE],		/* first four partitions */
	*buffers[MAXIMUM_PARTS]		/* pointers to buffers */
		= {buffer, buffer, buffer, buffer};

int	fd,				/* the disk */
	ext_index,			/* the prime extended partition */
	listing = 0,			/* no aborts for fdisk -l */
	size_flag = 0,
	partitions = 4;			/* maximum partition + 1 */

uint	heads,
	sectors,
	cylinders,
	display_factor = 1,		/* in units/sector */
	unit_flag = 1,
	full_bits = 0,			/* 1024 cylinders in sectors */
	extended_offset = 0,		/* offset of link pointers */
	offsets[MAXIMUM_PARTS] = {0, 0, 0, 0};

struct	partition *part_table[MAXIMUM_PARTS]	/* partitions */
		= {offset(buffer, 0), offset(buffer, 1),
		offset(buffer, 2), offset(buffer, 3)},
	*ext_pointers[MAXIMUM_PARTS]		/* link pointers */
		= {NULL, NULL, NULL, NULL};

struct systypes {
	unsigned char index;
	char *name;
	} sys_types[] = {
		{0, "Empty"},
		{1, "DOS 12-bit FAT"},
		{2, "XENIX root"},
		{3, "XENIX usr"},
		{4, "DOS 16-bit <32M"},
		{EXTENDED, "Extended"},
		{6, "DOS 16-bit >=32M"},
		{7, "OS/2 HPFS"},		/* or QNX? */
		{8, "AIX"},
		{9, "AIX bootable"},
		{10, "OPUS"},
		{0x40, "Venix 80286"},
		{0x51, "Novell?"},
		{0x52, "Microport"},		/* or CPM? */
		{0x63, "GNU HURD"},		/* or System V/386? */
		{0x64, "Novell"},
		{0x75, "PC/IX"},
		{0x80, "Old MINIX"},		/* Minix 1.4a and earlier */

		{LINUX_PARTITION, "Linux/MINIX"}, /* Minix 1.4b and later */
		{LINUX_SWAP, "Linux swap"},
		{LINUX_EXT, "Linux Ext"},

		{0x93, "Amoeba"},
		{0x94, "Amoeba BBT"},		/* (bad block table) */
		{0xb7, "BSDI fs"},
		{0xb8, "BSDI swap"},
		{0xc7, "Syrinx"},
		{0xdb, "CP/M"},			/* or Concurrent DOS? */
		{0xe1, "DOS access"},
		{0xe3, "DOS R/O"},
		{0xf2, "DOS secondary"},
		{0xff, "BBT"}			/* (bad track table) */
	};

jmp_buf listingbuf;

void fatal(enum failure why)
{
	char	error[LINE_LENGTH],
		*message = error;

	if (listing) {
		close(fd);
		longjmp(listingbuf, 1);
	}

	switch (why) {
		case usage: message =
			"Usage: fdisk [-l] [-v] [-s /dev/hdxn] [/dev/hdx]\n";
			break;
		case unable_to_open:
			sprintf(error, "Unable to open %s\n", disk_device);
			break;
		case unable_to_read:
			sprintf(error, "Unable to read %s\n", disk_device);
			break;
		case unable_to_seek:
			sprintf(error, "Unable to seek on %s\n", disk_device);
			break;
		case unable_to_write:
			sprintf(error, "Unable to write %s\n", disk_device);
			break;
		case out_of_memory:
			message = "Unable to allocate any more memory\n";
			break;
		default: message = "Fatal error\n";
	}
	fputs(message, stderr);
	exit(1);
}

void menu()
{
	puts("Command action\n"
		"   a   toggle a bootable flag\n"
		"   d   delete a partition\n"
		"   l   list known partition types\n"
		"   m   print this menu\n"
		"   n   add a new partition\n"
		"   p   print the partition table\n"
		"   q   quit without saving changes\n"
		"   t   change a partition's system id\n"
		"   u   change display/entry units\n"
		"   v   verify the partition table\n"
		"   w   write table to disk and exit\n"
		"   x   extra functionality (experts only)"
	);
}

void xmenu()
{
	puts("Command action\n"
		"   b   move beginning of data in a partition\n"
		"   c   change number of cylinders\n"
		"   e   list extended partitions\n"
		"   h   change number of heads\n"
		"   m   print this menu\n"
		"   p   print the partition table\n"
		"   q   quit without saving changes\n"
		"   r   return to main menu\n"
		"   s   change number of sectors\n"
		"   w   write table to disk and exit"
	);
}

char *partition_type(unsigned char type)
{
	int high = sizeof(sys_types) / sizeof(struct systypes),
		low = 0, mid;
	uint tmp;

	while (high >= low) {
		mid = (high + low) >> 1;
		if ((tmp = sys_types[mid].index) == type)
			return sys_types[mid].name;
		else if (tmp < type)
			low = mid + 1;
		else high = mid - 1;
	}
	return NULL;
}

void list_types()
{
	uint last[4], done = 0, next = 0,
		size = sizeof(sys_types) / sizeof(struct systypes);
	int i;

	for (i = 3; i >= 0; i--)
		last[3 - i] = done += (size + i - done) / (i + 1);
	i = done = 0;

	do {
		printf("%c%2x  %-15.15s", i ? ' ' : '\n',
			sys_types[next].index, sys_types[next].name);
		next = last[i++] + done;
		if (i > 3 || next >= last[i]) {
			i = 0;
			next = ++done;
		}
	} while (done < last[0]);
	putchar('\n');
}

void clear_partition(struct partition *p)
{
	p->boot_ind = 0;
	p->head = 0;
	p->sector = 0;
	p->cyl = 0;
	p->sys_ind = 0;
	p->end_head = 0;
	p->end_sector = 0;
	p->end_cyl = 0;
	p->start_sect = 0;
	p->nr_sects = 0;
}

void set_partition(int i, struct partition *p, uint start, uint stop,
	int sys, uint offset)
{
	p->boot_ind = 0;
	p->sys_ind = sys;
	p->start_sect = start - offset;
	p->nr_sects = stop - start + 1;
#if 0
	p->head = t_head(start);
	start /= heads;
	p->sector = t_sector(start);
	p->cyl = t_cylinder(start);
	p->end_head = t_head(stop);
	stop /= heads;
	p->end_sector = t_sector(stop);
	p->end_cyl = t_cylinder(stop);
#else
	set_hsc(p->head, p->sector, p->cyl, start);
	set_hsc(p->end_head, p->end_sector, p->end_cyl, stop);
#endif
	changed[i] = 1;
}

int test_c(char **m, char *mesg)
{
	int val = 0;
	if (!*m)
		printf("You must set");
	else {
		printf(" %s", *m);
		val = 1;
	}
	*m = mesg;
	return val;
}

int warn_geometry()
{
	char *m = NULL;
	int prev = 0;
	if (!heads)
		prev = test_c(&m, "heads");
	if (!sectors)
		prev = test_c(&m, "sectors");
	if (!cylinders)
		prev = test_c(&m, "cylinders");
	if (!m)
		return 0;
	fprintf(stderr,
		"%s%s.\nYou can do this from the extra functions menu.\n",
		prev ? " and " : " ", m);
	return 1;
}

uint rounded(uint calcul, uint start)
{
	uint i;
	if (!full_bits)
		return calcul;
	while ((i = calcul + full_bits) <= start)
		calcul = i;
	return calcul;
}

void update_units()
{
	full_bits = 1024 * heads * sectors;
	if (unit_flag && full_bits)
		display_factor = full_bits >> 10;
	else display_factor = 1;
}

void warn_cylinders()
{
	update_units();
	if (cylinders > 1024)
		fprintf(stderr, "The number of cylinders for this disk is "
			"set to %d.\nThis is larger than 1024, and may cause "
			"problems with some software.\n", cylinders);
}

void read_extended(struct partition *p)
{
	int i;
	struct partition *q;

	ext_pointers[ext_index] = part_table[ext_index];
	if (!p->start_sect)
		fprintf(stderr, "Bad offset in primary extended partition\n");
	else while (p->sys_ind == EXTENDED) {
		if (partitions >= MAXIMUM_PARTS) {
			fprintf(stderr,
				"Warning: deleting partitions after %d\n",
				partitions);
			clear_partition(ext_pointers[partitions - 1]);
			changed[partitions - 1] = 1;
			return;
		}
		offsets[partitions] = extended_offset + p->start_sect;
		if (!extended_offset)
			extended_offset = p->start_sect;
		if (lseek(fd, offsets[partitions] * SECTOR_SIZE, SEEK_SET) < 0)
			fatal(unable_to_seek);
		if (!(buffers[partitions] = (char *) malloc(SECTOR_SIZE)))
			fatal(out_of_memory);
		if (SECTOR_SIZE != read(fd, buffers[partitions], SECTOR_SIZE))
			fatal(unable_to_read);
		part_table[partitions] = ext_pointers[partitions] = NULL;
		q = p = offset(buffers[partitions], 0);
		for (i = 0; i < 4; i++, p++) {
			if (p->sys_ind == EXTENDED)
				if (ext_pointers[partitions])
					fprintf(stderr, "Warning: extra link "
						"pointer in partition table "
						"%d\n", partitions + 1);
				else
					ext_pointers[partitions] = p;
			else if (p->sys_ind)
				if (part_table[partitions])
					fprintf(stderr,
						"Warning: ignoring extra data "
						"in partition table %d\n",
						partitions + 1);
				else
					part_table[partitions] = p;
		}
		if (!part_table[partitions])
			if (q != ext_pointers[partitions])
				part_table[partitions] = q;
			else part_table[partitions] = q + 1;
		if (!ext_pointers[partitions])
			if (q != part_table[partitions])
				ext_pointers[partitions] = q;
			else ext_pointers[partitions] = q + 1;
		p = ext_pointers[partitions++];
	}
}

void get_boot()
{
	int i;
	struct hd_geometry geometry;

	if ((fd = open(disk_device, O_RDWR)) < 0)
		fatal(unable_to_open);
	if (SECTOR_SIZE != read(fd, buffer, SECTOR_SIZE))
		fatal(unable_to_read);
	if (!ioctl(fd, HDIO_REQ, &geometry)) {
		heads = geometry.heads;
		sectors = geometry.sectors;
		cylinders = geometry.cylinders;
		warn_cylinders();
	}
	else update_units();
	warn_geometry();

	for (i = 0; i < 4; i++)
		if(part_table[i]->sys_ind == EXTENDED)
			if (partitions != 4) {
				fprintf(stderr, "Ignoring extra extended "
					"partition %d\n", i + 1);
			}
			else read_extended(part_table[ext_index = i]);
	for (i = 3; i < partitions; i++)
		if (*table_check(buffers[i]) != PART_TABLE_FLAG) {
			fprintf(stderr, "Warning: invalid flag %04x of parti"
				"tion table %d will be corrected by w(rite)\n",
				*table_check(buffers[i]), i + 1);
			changed[i] = 1;
		}
}

int read_line()
{
	if (!fgets(line_buffer, LINE_LENGTH, stdin))
		return 0;
	line_ptr = line_buffer;
	while (*line_ptr && !isgraph(*line_ptr))
		line_ptr++;
	return *line_ptr;
}

char read_char(char *mesg)
{
	do
		fputs(mesg, stdout);
	while (!read_line());
	return *line_ptr;
}

uint read_int(uint low, uint high, char *mesg)
{
	uint i;
	char ms[70];
	sprintf(ms, "%s (%d-%d): ", mesg, low, high);

	while (1) {
		while (!isdigit(read_char(ms)) &&
			(!size_flag || *line_ptr != '+'));
		if (*line_ptr == '+') {
			i = atoi(++line_ptr);
			while (isdigit(*line_ptr))
				line_ptr++;
			switch (*line_ptr) {
				case 'c':
				case 'C': if (!unit_flag)
						i *= heads * sectors;
					break;
				case 'k':
				case 'K': i *= 2;
					i /= display_factor;
					break;
				case 'm':
				case 'M': i *= 2048;
					i /= display_factor;
					break;
				default: break;
			}
			i += low;
		}
		else i = atoi(line_ptr);
		if (i >= low && i <= high)
			break;
	}
	size_flag = 0;
	return i;
}

int get_partition(int warn, int max)
{
	int i = read_int(1, max, "Partition number") - 1;

	if (warn && !part_table[i]->sys_ind)
		printf("Warning: partition %d has empty type\n", i + 1);
	return i;
}

char *const str_units()
{
	return unit_flag ? "cylinder" : "sector";
}

void change_units()
{
	if (unit_flag = !unit_flag)
		display_factor = 1;
	else display_factor = heads * sectors;
	update_units();
	printf("Changing display/entry units to %ss\n",
		str_units());
}

void toggle_active(int i)
{
	struct partition *p = part_table[i];

	if (p->sys_ind == EXTENDED && !p->boot_ind)
		printf("WARNING: Partition %d is an extended partition\n",
			i + 1);
	if (p->boot_ind)
		p->boot_ind = 0;
	else p->boot_ind = ACTIVE_FLAG;
	changed[i] = 1;
}

void delete_partition(int i)
{
	struct partition *p = part_table[i], *q = ext_pointers[i];

/* Note that for the fifth partition (i == 4) we don't actually
 * decrement partitions.
 */

	if (warn_geometry())
		return;
	changed[i] = 1;
	if (i < 4) {
		if (p->sys_ind == EXTENDED && i == ext_index) {
			while (partitions > 4)
				free(buffers[--partitions]);
			ext_pointers[ext_index] = NULL;
			extended_offset = 0;
		}
		clear_partition(p);
	}
	else if (!q->sys_ind && i > 4) {
		free(buffers[--partitions]);
		clear_partition(ext_pointers[--i]);
	}
	else if (i > 3) {
		if (i > 4) {
			p = ext_pointers[i - 1];
			p->boot_ind = 0;
			p->head = q->head;
			p->sector = q->sector;
			p->cyl = q->cyl;
			p->sys_ind = EXTENDED;
			p->end_head = q->end_head;
			p->end_sector = q->end_sector;
			p->end_cyl = q->end_cyl;
			p->start_sect = q->start_sect;
			p->nr_sects = q->nr_sects;
			changed[i - 1] = 1;
		}
		else {
			part_table[5]->start_sect +=
				offsets[5] - extended_offset;
			offsets[5] = extended_offset;
			changed[5] = 1;
		}
		if (partitions > 5) {
			partitions--;
			free(buffers[i]);
			while (i < partitions) {
				changed[i] = changed[i + 1];
				buffers[i] = buffers[i + 1];
				offsets[i] = offsets[i + 1];
				part_table[i] = part_table[i + 1];
				ext_pointers[i] = ext_pointers[i + 1];
				i++;
			}
		}
		else
			clear_partition(part_table[i]);
	}
}

void change_sysid()
{
	char *temp;
	int i = get_partition(0, partitions), sys;
	struct partition *p = part_table[i];

	if ((sys = p->sys_ind) == EXTENDED)
		printf("Partition %d is extended.  Delete it\n", i + 1);
	else if (!sys)
		printf("Partition %d does not exist yet!\n", i + 1);
	else while (1) {
		read_char("Hex code (type L to list codes): ");
		if (tolower(*line_ptr) == 'l')
			list_types();
		else if (isxdigit(*line_ptr)) {
			sys = 0;
			do
				sys = sys << 4 | hex_val(*line_ptr++);
			while (isxdigit(*line_ptr));
			if (!sys) {
				delete_partition(i);
				break;
			}
			else if (sys == EXTENDED) {
				printf("You may not change a partition "
					"to be an extended partition\n");
				break;
			}
			else if (sys < 256) {
				if (sys == p->sys_ind)
					break;
				part_table[i]->sys_ind = sys;
				printf ("Changed system type of partition %d "
					"to %x (%s)\n", i + 1, sys,
					(temp = partition_type(sys)) ? temp :
					"Unknown");
				changed[i] = 1;
				break;
			}
		}
	}
}

/* check_consistency() and long2chs() added Sat Mar 6 12:28:16 1993,
 * faith@cs.unc.edu, based on code fragments from pfdisk by Gordon W. Ross,
 * Jan.  1990 (version 1.2.1 by Gordon W. Ross Aug. 1990; Modified by S.
 * Lubkin Oct.  1991). */

static void long2chs(ulong ls, uint *c, uint *h, uint *s)
{
	int	spc = heads * sectors;

	*c = ls / spc;
	ls = ls % spc;
	*h = ls / sectors;
	*s = ls % sectors + 1;	/* sectors count from 1 */
}

static void check_consistency(struct partition *p, int partition)
{
	uint	pbc,pbh,pbs;		/* physical beginning c,h,s */
	uint	pec,peh,pes;		/* physical ending c,h,s */
	uint	lbc,lbh,lbs;		/* logical beginning c,h,s */
	uint	lec,leh,les;		/* logical ending c,h,s */
	ulong	lsbeg,lslen;		/* logical sectors: begin, length */

	if (partition >= 4) return;	/* do not check extended partitions */

/* physical beginning c,h,s */
	pbc = p->cyl & 0xff | (p->sector << 2) & 0x300;
	pbh = p->head;
	pbs = p->sector & 0x3F;

/* physical ending c,h,s */
	pec = p->end_cyl & 0xff | (p->end_sector << 2) & 0x300;
	peh = p->end_head;
	pes = p->end_sector & 0x3F;

/* compute logical beginning (c,h,s) */
	long2chs(p->start_sect, &lbc, &lbh, &lbs);

/* compute logical ending (c,h,s) */
	long2chs(p->start_sect + p->nr_sects - 1, &lec, &leh, &les);

/* Same physical / logical beginning? */
	if (pbc != lbc || pbh != lbh || pbs != lbs) {
		printf("Partition %d has different physical/logical "
			"beginnings:\n", partition+1);
		printf("     phys=(%d,%d,%d) ", pbc, pbh, pbs);
		printf("logical=(%d,%d,%d)\n",lbc, lbh, lbs);
	}

/* Same physical / logical ending? */
	if (pec != lec || peh != leh || pes != les) {
		printf("Partition %d has different phsyical/logical "
			"endings:\n", partition+1);
		printf("     phys=(%d,%d,%d) ", pec, peh, pes);
		printf("logical=(%d,%d,%d)\n",lec, leh, les);
	}

#if 1
	/* Beginning on cylinder boundary? */
#if 0
	if (pbc == 0) { /* exception: start on head 1 */
		if (pbh != 1 || pbs != 1) {
			printf("warning: first(%i): ", i+1);
			printf("phys=(%d,%d,%d) ", pbc, pbh, pbs);
			printf("should be (%d,1,1)\n", pbc);
		}
	}
#endif
	if (pbc) { /* not on cyl 0 */
		if (pbh != 0 || pbs != 1) {
			printf("Partition %i does not start on cylinder "
				"boundary:\n", partition+1);
			printf("     phys=(%d,%d,%d) ", pbc, pbh, pbs);
			printf("should be (%d,0,1)\n", pbc);
		}
	}

/* Ending on cylinder boundary? */
	if (peh != (heads-1) || pes != sectors) {
		printf("Partition %i does not end on cylinder boundary:\n",
			partition+1);
		printf("     phys=(%d,%d,%d) ", pec, peh, pes);
		printf("should be (%d,%d,%d)\n",
		pec, heads-1, sectors);
	}
#endif
}

void list_table()
{
	struct partition *p;
	char *type;
	int i, w = strlen(disk_device);

	printf("\nDisk %s: %d heads, %d sectors, %d cylinders\nUnits = "
		"%ss of %d * 512 bytes\n\n", disk_device, heads, sectors,
		cylinders, str_units(), display_factor);
	if (w < 5)
		w = 5;
	printf("%*s Boot  Begin   Start     End  Blocks   Id  System\n",
		w + 1, "Device");
	for (i = 0 ; i < partitions; i++)
		if ((p = part_table[i])->sys_ind) {
			printf("%*s%-2d  %c%8d%8d%8d%8d%c  %2x  %s\n", w,
			disk_device, i + 1,
			!p->boot_ind ? ' ' : p->boot_ind == ACTIVE_FLAG
			? '*' : '?',
			cround(rounded( calculate(p->head, p->sector, p->cyl),
				p->start_sect + offsets[i])),
			cround(p->start_sect + offsets[i]),
			cround(p->start_sect + offsets[i] + p->nr_sects
				- (p->nr_sects ? 1: 0)),
			p->nr_sects / 2, p->nr_sects & 1 ? '+' : ' ',
			p->sys_ind,
			(type = partition_type(p->sys_ind)) ?
			type : "Unknown");
			check_consistency(p, i);
		}

}

void x_list_table(int extend)
{
	struct partition *p, **q;
	int i;

	if (extend)
		q = ext_pointers;
	else
		q = part_table;
	printf("\nDisk %s: %d heads, %d sectors, %d cylinders\n\n",
		disk_device, heads, sectors, cylinders);
	printf("Nr AF  Hd Sec  Cyl  Hd Sec  Cyl  Start   Size ID\n");
	for (i = 0 ; i < partitions; i++) {
		if (p = q[i]) {
			printf("%2d %02x%4d%4d%5d%4d%4d%5d%7d%7d %02x\n",
				i + 1, p->boot_ind, p->head,
				sector(p->sector),
				cylinder(p->sector, p->cyl), p->end_head,
				sector(p->end_sector),
				cylinder(p->end_sector, p->end_cyl),
				p->start_sect, p->nr_sects, p->sys_ind);
			if (p->sys_ind)
				check_consistency(p, i);
		}
	}
}

void check_bounds(uint *first, uint *last)
{
	int i;
	uint max = 256 * 63 * 1024;
	struct partition *p = part_table[0];

	for (i = 0; i < partitions; p = part_table[++i])
		if (!p->sys_ind || p->sys_ind == EXTENDED) {
			first[i] = max;
			last[i] = 0;
		}
		else {
			first[i] = rounded(calculate(p->head, p->sector,
				p->cyl), p->start_sect + offsets[i]);
			last[i] = p->start_sect + offsets[i] + p->nr_sects - 1;
		}
}

void check(int n, uint h, uint s, uint c, uint start)
{
	uint total, real_s, real_c, i;

	real_s = sector(s) - 1;
	real_c = cylinder(s, c);
	total = (real_c * sectors + real_s) * heads + h;
	if (full_bits)
		while ((i = total + full_bits) <= start) {
			real_c += 1024;
			total = i;
		}
	if (!total)
		printf("Warning: partition %d contains sector 0\n", n);
	if (h >= heads)
		printf("Partition %d: head %d greater than maximum %d\n",
			n, h + 1, heads);
	if (real_s >= sectors)
		printf("Partition %d: sector %d greater than "
			"maximum %d\n", n, s, sectors);
	if (real_c >= cylinders)
		printf("Partitions %d: cylinder %d greater than "
			"maximum %d\n", n, real_c + 1, cylinders);
	if (start != total)
		printf("Partition %d: previous sectors %d disagrees with "
			"total %d\n", n, start, total);
}


void verify()
{
	int i, j;
	uint total = 1,
		first[partitions], last[partitions];
	struct partition *p = part_table[0];

	if (warn_geometry())
		return;

	check_bounds(first, last);
	for (i = 0; i < partitions; p = part_table[++i])
		if (p->sys_ind && (p->sys_ind != EXTENDED)) {
			check_consistency(p, i);
			if (p->start_sect + offsets[i] < first[i])
				printf("Warning: bad start-of-data in "
				"partition %d\n", i + 1);
			check(i + 1, p->end_head, p->end_sector, p->end_cyl,
				last[i]);
			total += last[i] + 1 - first[i];
			for (j = 0; j < i; j++)
			if (first[i] >= first[j] && first[i] <= last[j]
					|| (last[i] <= last[j] &&
					last[i] >= first[j])) {
				printf("Warning: partition %d overlaps "
					"partition %d.\n", j + 1, i + 1);
				total += first[i] >= first[j] ?
					first[i] : first[j];
				total -= last[i] <= last[j] ?
					last[i] : last[j];
			}
		}

	if (extended_offset) {
		uint e_last = part_table[ext_index]->start_sect +
			part_table[ext_index]->nr_sects - 1;

		for (p = part_table[i = 4]; i < partitions;
				p = part_table[++i]) {
			total++;
			if (!p->sys_ind) {
			if (i != 4 || i + 1 < partitions)
				printf("Warning: partition %d "
					"is empty\n", i + 1);
			}
			else if (first[i] < extended_offset ||
					last[i] > e_last)
				printf("Logical partition %d not entirely in "
					"partition %d\n", i + 1, ext_index + 1);
		}
	}

	if (total > heads * sectors * cylinders)
		printf("Total allocated sectors %d greater than the maximum "
			"%d\n", total, heads * sectors * cylinders);
	else if (total = heads * sectors * cylinders - total)
		printf("%d unallocated sectors\n", total);
}

void add_partition(int n, int sys)
{
	char mesg[48];
	int i, read = 0;
	struct partition *p = part_table[n], *q = part_table[ext_index];
	uint start, stop, limit, temp,
		first[partitions], last[partitions];

	if (p->sys_ind) {
		printf("Partition %d is already defined.  Delete "
			"it before re-adding it.\n", n + 1);
		return;
	}
	check_bounds(first, last);
	if (n < 4) {
		start = 1;
		limit = heads * sectors * cylinders - 1;
		if (extended_offset) {
			first[ext_index] = extended_offset;
			last[ext_index] = q->start_sect + q->nr_sects - 1;
		}
	}
	else {
		start = extended_offset + 1;
		limit = q->start_sect + q->nr_sects - 1;
	}
	if (unit_flag)
		for (i = 0; i < partitions; i++)
			first[i] = (cround(first[i]) - 1) * display_factor;

	sprintf(mesg, "First %s", str_units());
	do {
		temp = start;
		for (i = 0; i < partitions; i++) {
			if (start == offsets[i])
				start++;
			if (start >= first[i] && start <= last[i])
				start = last[i] + 1;
		}
		if (start > limit)
			break;
		if (start != temp && read) {
			printf("Sector %d is already allocated\n", temp);
			temp = start = stop;
			read = 0;
		}
		if (!read && start == temp) {
			uint i;
			temp = 0;
			start = read_int(cround(i = (stop = start) + (n > 4)),
				cround(limit), mesg);
			if (unit_flag) {
				start = (start - 1) * display_factor;
				if (start < i) start = i;
				}
			read = 1;
		}
	} while (start != temp || !read);
	if (n > 4)			/* NOT for fifth partition */
		offsets[n] = start - 1;

	for (i = 0; i < partitions; i++) {
		if (start < offsets[i] && limit >= offsets[i])
			limit = offsets[i] - 1;
		if (start < first[i] && limit >= first[i])
			limit = first[i] - 1;
	}
	if (start > limit) {
		printf("No free sectors available\n");
		if (n > 4) {
			free(buffers[n]);
			partitions--;
		}
		return;
	}
	if (cround(start) == cround(limit))
		stop = start;
	else {
		sprintf(mesg, "Last %s or +size or +sizeM or +sizeK",
			str_units());
		size_flag = 1;
		stop = read_int(cround(start), cround(limit), mesg);
		if (unit_flag) {
			stop = stop * display_factor - 1;
			if (stop >limit)
				stop = limit;
		}
	}

	set_partition(n, p, start, stop, sys, offsets[n]);

	if (sys == EXTENDED) {
		ext_index = n;
		offsets[4] = extended_offset = start;
		ext_pointers[n] = p;
		if (!(buffers[4] = calloc(1, SECTOR_SIZE)))
			fatal(out_of_memory);
		part_table[4] = offset(buffers[4], 0);
		ext_pointers[4] = part_table[4] + 1;
		changed[4] = 1;
		partitions = 5;
	}
	else {
		if (n > 4)
			set_partition(n - 1, ext_pointers[n - 1], start - 1,
				stop, EXTENDED, extended_offset);
		if ((limit = p->nr_sects) & 1)
			printf("Warning: partition %d has an odd "
				"number of sectors.\n", n + 1);
		if (limit > 131070)
			printf("Warning: The Linux/MINIX filesystem cannot "
				"currently use %d sectors of this partition\n",
				limit - 131070);
	}
}

void add_logical()
{
	if (partitions > 5 || part_table[4]->sys_ind) {
		if (!(buffers[partitions] = calloc(1, SECTOR_SIZE)))
			fatal(out_of_memory);
		part_table[partitions] = offset(buffers[partitions], 0);
		ext_pointers[partitions] = part_table[partitions] + 1;
		offsets[partitions] = 0;
		partitions++;
	}
	add_partition(partitions - 1, LINUX_PARTITION);
}

void new_partition()
{
	int i, free_primary = 0;

	if (warn_geometry())
		return;
	if (partitions >= MAXIMUM_PARTS) {
		printf("The maximum number of partitions has been created\n");
		return;
	}

	for (i = 0; i < 4; i++)
		free_primary += !part_table[i]->sys_ind;
	if (!free_primary)
		if (extended_offset)
			add_logical();
		else
			printf("You must delete some partition and add "
				"an extended partition first\n");
	else {
		char c, line[LINE_LENGTH];
		sprintf(line, "Command action\n   %s\n   p   primary "
			"partition (1-4)\n", extended_offset ?
			"l   logical (5 or over)" : "e   extended");
		while (1) {
			if ((c = tolower(read_char(line))) == 'p') {
				add_partition(get_partition(0, 4),
					LINUX_PARTITION);
				return;
			}
			else if (c == 'l' && extended_offset) {
				add_logical();
				return;
			}
			else if (c == 'e' && !extended_offset) {
				add_partition(get_partition(0, 4),
					EXTENDED);
				return;
			}
		}
	}
}

void write_table()
{
	int i;

	changed[3] = changed[0] || changed[1] || changed[2] || changed[3];
	for (i = 3; i < partitions; i++)
		if (changed[i]) {
			*table_check(buffers[i]) = PART_TABLE_FLAG;
			if (lseek(fd, offsets[i] * SECTOR_SIZE, SEEK_SET) < 0)
				fatal(unable_to_seek);
			if (write(fd, buffers[i], SECTOR_SIZE) != SECTOR_SIZE)
				fatal(unable_to_write);
	}

	printf("The partition table has been altered!\n\n");

	printf("Calling BLKRRPART ioctl() to re-read partition table\n");
	sync();
	sleep(2);
	ioctl(fd, BLKRRPART);
	close(fd);

	printf("Syncing disks\n");
	sync();
	sleep(4);		/* for sync() */

	printf("Reboot your system to ensure partition table is updated\n");

	exit(0);
}

void move_begin(int i)
{
	struct partition *p = part_table[i];
	uint new, first;

	if (warn_geometry())
		return;
	if (!p->sys_ind || !p->nr_sects || p->sys_ind == EXTENDED) {
		printf("Partition %d has no data area\n", i + 1);
		return;
	}
	first = rounded(calculate(p->head, p->sector, p->cyl), p->start_sect +
		offsets[i]);
	new = read_int(first, p->start_sect + p->nr_sects + offsets[i] - 1,
		"New beginning of data") - offsets[i];

	if (new != p->nr_sects) {
		first = p->nr_sects + p->start_sect - new;
		p->nr_sects = first;
		p->start_sect = new;
		changed[i] = 1;
	}
}

void xselect()
{
	while(1) {
		putchar('\n');
		switch (tolower(read_char("Expert command (m for help): "))) {
			case 'b': move_begin(get_partition(0, partitions));
				break;
			case 'c': cylinders = read_int(1, 65535,
					"Number of cylinders");
				warn_cylinders();
				break;
			case 'e': x_list_table(1);
				break;
			case 'h': heads = read_int(1, 256, "Number of heads");
				update_units();
				break;
			case 'p': x_list_table(0);
				break;
			case 'q': close(fd);
				exit(0);
			case 'r': return;
			case 's': sectors = read_int(1, 63,
					"Number of sectors");
				update_units();
				break;
			case 'w': write_table();
			default: xmenu();
		}
	}
}

void try(char *device)
{
	disk_device = device;
	if (!setjmp(listingbuf))
	if ((fd = open(disk_device, O_RDWR)) >= 0) {
		close(fd);
		get_boot();
		list_table();
	}
}

void main(int argc, char **argv)
{
	if (argc > 3)
		fatal(usage);
	if (argc > 1 && *argv[1] == '-') {
		switch (*(argv[1]+1)) {
			case 'v':
				printf("fdisk v" VERSION "\n");
				exit(0);
			case 'l':
				listing = 1;
				try("/dev/hda");
				try("/dev/hdb");
				try("/dev/sda");
				try("/dev/sdb");
				try("/dev/sdc");
				try("/dev/sdd");
				try("/dev/sde");
				try("/dev/sdf");
				try("/dev/sdg");
				try("/dev/sdh");
				exit(0);
			case 's': {
				int i;
				if (argc < 3)
					fatal(usage);
				if (!(i = atoi(argv[2] + 8)))
					fatal(usage);
				disk_device = (char *) malloc(9);
				strncpy(disk_device, argv[2], 8);
				if ((fd = open(disk_device, O_RDWR)) >= 0) {
					close(fd);
					get_boot();
				if(part_table[--i]->sys_ind > 10)
					printf("%d\n",
						part_table[i]->nr_sects / 2);
				else exit(1);
				exit(0);
			}
	}
			default:
				fatal(usage);
		}
	}
	if (argc > 1)
		disk_device = argv[argc - 1];
	else {
		if ((fd = open(DEFAULT_DEVICE, O_RDWR)) < 0) {
			disk_device = ALTERNATE_DEVICE;
		}
		else {
			close(fd);
		}
	}
	get_boot();
	if (argc == 1)
		printf("Using %s as default device!\n", disk_device);

	while (1) {
		putchar('\n');
		switch (tolower(read_char("Command (m for help): "))) {
			case 'a': toggle_active(get_partition(1, partitions));
				break;
			case 'd': delete_partition(
					get_partition(1, partitions));
				break;
			case 'l': list_types();
				break;
			case 'n': new_partition();
				break;
			case 'p': list_table();
				break;
			case 'q': close(fd);
				exit(0);
			case 't': change_sysid();
				break;
			case 'u': change_units();
				break;
			case 'v': verify();
				break;
			case 'w': write_table();
			case 'x': xselect();
				break;
			default: menu();
		}
	}
}
