/****************************************************************************
 *
 *     CFDISK
 *
 * cfdisk is a curses based disk drive partitioning program that can
 * create partitions for a wide variety of operating systems including
 * Linux, MS-DOS and OS/2.
 *
 * cfdisk was inspired by the fdisk program, by A. V. Le Blanc
 * (LeBlanc@mcc.ac.uk).
 *
 *     Copyright (C) 1994 Kevin E. Martin (martin@cs.unc.edu)
 *
 * cfdisk 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 2 of the License, or
 * (at your option) any later version.
 *
 * cfdisk is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with cfdisk; if not, write to the Free Software Foundation,
 * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Created:	Fri Jan 28 22:46:58 1994, martin@cs.unc.edu
 * >2GB patches: Sat Feb 11 09:08:10 1995, faith@cs.unc.edu
 * Prettier menus: Sat Feb 11 09:08:25 1995, Janne Kukonlehto
 *                                           <jtklehto@stekt.oulu.fi>
 * Versions 0.8e-h: aeb@cwi.nl
 *
 ****************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <fcntl.h>
#include <curses.h>
#include <signal.h>
#include <math.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/fs.h>		/* for BLKRRPART */

#if defined(__GNUC__) || defined(HAS_LONG_LONG)
typedef long long ext2_loff_t;
#else
typedef long      ext2_loff_t;
#endif

extern ext2_loff_t ext2_llseek(unsigned int fd, ext2_loff_t offset,
			       unsigned int origin);

#define VERSION "0.8i"

#define DEFAULT_DEVICE "/dev/hda"
#define ALTERNATE_DEVICE "/dev/sda"

#define LINE_LENGTH 80
#define MAXIMUM_PARTS 60

#define SECTOR_SIZE 512

#define MAX_CYLINDERS 65535
#define MAX_HEADS 255
#define MAX_SECTORS 63

#define ACTIVE_FLAG 0x80
#define PART_TABLE_FLAG 0xAA55

#define UNUSABLE -1
#define FREE_SPACE 0x00
#define DOS_EXTENDED 0x05
#define LINUX_EXTENDED 0x85
#define LINUX_MINIX 0x81
#define LINUX_SWAP 0x82
#define LINUX 0x83

#define ADD_EXISTS "This partition is already in use"
#define ADD_UNUSABLE "This partition is unusable"
#define DEL_EMPTY "Cannot delete an empty partition"
#define ID_EMPTY "Cannot change FS Type to empty"
#define ID_EXT "Cannot change FS Type to extended"
#define NEED_EXT "No room to create the extended partition"
#define NO_FLAGS "Cannot make this partition bootable"
#define NO_MORE_PARTS "No more partitions"
#define PRINT_OPEN_ERR "Cannot open file '%s'"
#define TWO_EXTENDEDS "Cannot create logical drive here -- would create two extended partitions"
#define TYPE_EMPTY "Cannot change the type of an empty partition"
#define BAD_COMMAND "Illegal command"
#define MAX_UNMAXABLE "Cannot maximize this partition"
#define BAD_OPEN "Cannot open disk drive"
#define BAD_SEEK "Cannot seek on disk drive"
#define BAD_READ "Cannot read disk drive"
#define BAD_WRITE "Cannot write disk drive"
#define BAD_GEOMETRY "Cannot read disk drive geometry"
#define BAD_PRIMARY "Bad primary partition"
#define BAD_LOGICAL "Bad logical partition"
#define BAD_CYLINDERS "Illegal cylinders value"
#define BAD_HEADS "Illegal heads value"
#define BAD_SECTORS "Illegal sectors value"
#define READONLY_WARN "Opened disk read-only - you have no permission to write"
#define WRITE_WARN "Warning!!  This may destroy data on your disk!"
#define YES_NO "Please enter `yes' or `no'"
#define WRITING_PART "Writing partition table to disk..."
#define YES_WRITE "Wrote partition table to disk"
#define NO_WRITE "Did not write partition table to disk"
#define RRPART_FAILED "Wrote partition table, but re-read table failed.  Reboot to update table."

#define PRI_OR_LOG -1
#define PRIMARY -2
#define LOGICAL -3

#define COL_ID_WIDTH 20

#define CR '\015'
#define ESC '\033'
#define DEL '\177'
#define BELL '\007'
/* '\014' == ^L */
#define REDRAWKEY '\014'

/* Display units */
#define MEGABYTES 1
#define SECTORS 2
#define CYLINDERS 3

#define GS_DEFAULT -1
#define GS_ESCAPE -2

#define PRINT_RAW_TABLE 1
#define PRINT_SECTOR_TABLE 2
#define PRINT_PARTITION_TABLE 4

#define IS_PRIMARY(p) ((p) >= 0 && (p) < 4)
#define IS_LOGICAL(p) ((p) > 3)

#define round_int(d) ((double)((int)(d+0.5)))
#define ceiling(d) ((double)(((d) != (int)(d)) ? (int)(d+1.0) : (int)(d)))

#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;\
}

#define is_extended(x)	((x) == DOS_EXTENDED || (x) == LINUX_EXTENDED)

/* we might also want to recognise 0xe and 0xf */
#define is_dos_partition(x) ((x) == 1 || (x) == 4 || (x) == 6)

#define ALIGNMENT 2
typedef union {
    struct {
	unsigned char align[ALIGNMENT];
	unsigned char b[SECTOR_SIZE];
    } c;
    struct {
	unsigned char align[ALIGNMENT];
	unsigned char buffer[0x1BE];
	struct partition part[4];
	unsigned short flag;
    } p;
} partition_table;

typedef struct {
    int first_sector;	/* first sector in partition */
    int last_sector;	/* last sector in partition */
    int offset;		/* offset from first sector to start of data */
    int flags;		/* active == 0x80 */
    int id;		/* filesystem type */
    int num;		/* number of partition -- primary vs. logical */
#define LABELSZ 11
    char dos_label[LABELSZ+1];
} partition_info;

char *disk_device = DEFAULT_DEVICE;
int fd;
int heads = 0;
int sectors = 0;
int cylinders = 0;
int changed = FALSE;
int opened = FALSE;
int opentype;
int curses_started = 0;

partition_info p_info[MAXIMUM_PARTS];
partition_info ext_info;
int num_parts = 0;

int logical = 0;
int logical_sectors[MAXIMUM_PARTS];

__sighandler_t old_SIGINT, old_SIGTERM;

int arrow_cursor = FALSE;
int display_units = MEGABYTES;
int zero_table = FALSE;
int print_only = 0;

/* Curses screen information */
int cur_part = 0;
int warning_last_time = FALSE;
int defined = FALSE;
int COLUMNS = 80;
int NUM_ON_SCREEN = 1;

/* Y coordinates */
int HEADER_START = 0;
int DISK_TABLE_START = 5;
int WARNING_START = 23;
int COMMAND_LINE_Y = 21;

/* X coordinates */
int NAME_START = 4;
int FLAGS_START = 16;
int PTYPE_START = 30;
int FSTYPE_START = 45;
int SIZE_START = 70;
int COMMAND_LINE_X = 5;

#define NUM_PART_TYPES 256
char *partition_type[NUM_PART_TYPES] = {
    [LINUX_MINIX] = "Linux/MINIX",
    [LINUX_SWAP]  = "Linux Swap",
    [LINUX]       = "Linux",
    [FREE_SPACE]  = "Free Space",
    [DOS_EXTENDED]= "Extended",
    [LINUX_EXTENDED] = "Linux extended",
    [0x01]        = "DOS FAT12",
    [0x04]        = "DOS FAT16",
    [0x06]        = "DOS FAT16 (big)",
    [0x07]        = "OS/2 HPFS or NTFS",
    [0x0A]        = "OS/2 Boot Manager",
    [0x0B]        = "Win95 FAT32",
    [0x0C]        = "Win95 FAT32 (LBA)",
    [0x0E]        = "Win95 FAT16 (LBA)",
    [0x0F]        = "Win95 Extended (LBA)",
    [0x11]        = "Hidden DOS FAT12",
    [0x14]        = "Hidden DOS FAT16",
    [0x16]        = "Hidden DOS FAT16 (big)",
    [0xA5]        = "BSD/386",

/* The rest of these are taken from A. V. Le Blanc's (LeBlanc@mcc.ac.uk)
 * fdisk program.  I do not know where they came from, but I include
 * them for completeness.  (With additions.)
 */

    [0x02]        = "XENIX root",
    [0x03]        = "XENIX usr",
    [0x08]        = "AIX",
    [0x09]        = "AIX bootable",
    [0x40]        = "Venix 80286",
    [0x51]        = "Novell?",
    [0x52]        = "Microport",
    [0x63]        = "GNU HURD",
    [0x64]        = "Novell Netware 286",
    [0x65]        = "Novell Netware 386",
    [0x75]        = "PC/IX",
    [0x80]        = "Old MINIX",
    [0x93]        = "Amoeba",
    [0x94]        = "Amoeba BBT",
    [0xB7]        = "BSDI fs",
    [0xB8]        = "BSDI swap",
    [0xC7]        = "Syrinx",
    [0xDB]        = "CP/M",
    [0xE1]        = "DOS access",
    [0xE3]        = "DOS R/O",
    [0xF2]        = "DOS secondary",
    [0xFF]        = "BBT"
};

void fdexit(int ret)
{
    if (opened)
	close(fd);

    if (changed) {
	fprintf(stderr, "Disk has been changed.\n");
	fprintf(stderr, "Reboot the system to ensure the partition "
			"table is correctly updated.\n");
	
	fprintf( stderr, "\nWARNING: If you have created or modified any\n"
		         "DOS 6.x partitions, please see the cfdisk manual\n"
		         "page for additional information.\n" );
    }

    exit(ret);
}

int get_string(char *str, int len, char *def)
{
    char c;
    int i = 0;
    int x, y;
    int use_def = FALSE;

    getyx(stdscr, y, x);
    clrtoeol();

    str[i] = 0;

    if (def != NULL) {
	mvaddstr(y, x, def);
	move(y, x);
	use_def = TRUE;
    }

    refresh();
    while ((c = getch()) != '\n' && c != CR) {
	switch (c) {
	case ESC:
	    move(y, x);
	    clrtoeol();
	    refresh();
	    return GS_ESCAPE;
	case DEL:
	case '\b':
	    if (i > 0) {
		str[--i] = 0;
		mvaddch(y, x+i, ' ');
		move(y, x+i);
	    } else if (use_def) {
		clrtoeol();
		use_def = FALSE;
	    } else
		putchar(BELL);
	    break;
	default:
	    if (i < len && isprint(c)) {
		mvaddch(y, x+i, c);
		if (use_def) {
		    clrtoeol();
		    use_def = FALSE;
		}
		str[i++] = c;
		str[i] = 0;
	    } else
		putchar(BELL);
	}
	refresh();
    }

    if (use_def)
	return GS_DEFAULT;
    else
	return i;
}

void clear_warning(void)
{
    int i;

    if (!curses_started || !warning_last_time)
	return;

    move(WARNING_START,0);
    for (i = 0; i < COLS; i++)
	addch(' ');

    warning_last_time = FALSE;
}

void print_warning(char *s)
{
    if (!curses_started) {
	 fprintf(stderr, "%s\n", s);
    } else {
	mvaddstr(WARNING_START, (COLS-strlen(s))/2, s);
	putchar(BELL); /* CTRL-G */

	warning_last_time = TRUE;
    }
}

void die_x(int ret);

void fatal(char *s)
{
    char str[LINE_LENGTH];

    if (curses_started) {
	 sprintf(str, "FATAL ERROR: %s", s);
	 mvaddstr(WARNING_START, (COLS-strlen(str))/2, str);
	 sprintf(str, "Press any key to exit fdisk");
	 mvaddstr(WARNING_START+1, (COLS-strlen(str))/2, str);
	 putchar(BELL); /* CTRL-G */
	 refresh();
	 (void)getch();
	 die_x(1);
    } else {
	 fprintf(stderr, "FATAL ERROR: %s\n", s);
	 exit(1);
    }
}

void die(int dummy)
{
    die_x(0);
}

void die_x(int ret)
{
    signal(SIGINT, old_SIGINT);
    signal(SIGTERM, old_SIGTERM);
    mvcur(0, COLS-1, LINES-1, 0);
    nl();
    endwin();
    printf("\n");
    fdexit(ret);
}

void read_sector(char *buffer, int sect_num)
{
    if (ext2_llseek(fd, ((ext2_loff_t) sect_num)*SECTOR_SIZE, SEEK_SET) < 0)
	fatal(BAD_SEEK);
    if (read(fd, buffer, SECTOR_SIZE) != SECTOR_SIZE)
	fatal(BAD_READ);
}

void write_sector(char *buffer, int sect_num)
{
    if (ext2_llseek(fd, ((ext2_loff_t) sect_num)*SECTOR_SIZE, SEEK_SET) < 0)
	fatal(BAD_SEEK);
    if (write(fd, buffer, SECTOR_SIZE) != SECTOR_SIZE)
	fatal(BAD_WRITE);
}

void get_dos_label(int i)
{
    char label[LABELSZ+1];
    ext2_loff_t offset;
    int j;

    offset = ((ext2_loff_t) p_info[i].first_sector + p_info[i].offset)
	 * SECTOR_SIZE + 43;
    if (ext2_llseek(fd, offset, SEEK_SET) == offset
	&& read(fd, &label, LABELSZ) == LABELSZ) {
	 for(j=0; j<LABELSZ; j++)
	      if(!isascii(label[j]))
		   label[j] = 0;
	 label[LABELSZ] = 0;
	 strcpy(p_info[i].dos_label, label);
    }
}

void check_part_info(void)
{
    int i, pri = 0, log = 0;

    for (i = 0; i < num_parts; i++)
	if (p_info[i].id > 0 && IS_PRIMARY(p_info[i].num))
	    pri++;
	else if (p_info[i].id > 0 && IS_LOGICAL(p_info[i].num))
	    log++;
    if (is_extended(ext_info.id))
	if (log > 0)
	    pri++;
	else {
	    ext_info.first_sector = 0;
	    ext_info.last_sector = 0;
	    ext_info.offset = 0;
	    ext_info.flags = 0;
	    ext_info.id = FREE_SPACE;
	    ext_info.num = PRIMARY;
	}

    if (pri >= 4) {
	for (i = 0; i < num_parts; i++)
	    if (p_info[i].id == FREE_SPACE || p_info[i].id == UNUSABLE)
		if (is_extended(ext_info.id))
		    if (p_info[i].first_sector >= ext_info.first_sector &&
			p_info[i].last_sector <= ext_info.last_sector) {
			p_info[i].id = FREE_SPACE;
			p_info[i].num = LOGICAL;
		    } else if (i > 0 &&
			       p_info[i-1].first_sector >=
			       ext_info.first_sector &&
			       p_info[i-1].last_sector <=
			       ext_info.last_sector) {
			p_info[i].id = FREE_SPACE;
			p_info[i].num = LOGICAL;
		    } else if (i < num_parts-1 &&
			       p_info[i+1].first_sector >=
			       ext_info.first_sector &&
			       p_info[i+1].last_sector <=
			       ext_info.last_sector) {
			p_info[i].id = FREE_SPACE;
			p_info[i].num = LOGICAL;
		    } else
			p_info[i].id = UNUSABLE;
		else /* if (!is_extended(ext_info.id)) */
		    p_info[i].id = UNUSABLE;
	    else /* if (p_info[i].id > 0) */
		while (0); /* Leave these alone */
    } else { /* if (pri < 4) */
	for (i = 0; i < num_parts; i++) {
	    if (p_info[i].id == UNUSABLE)
		p_info[i].id = FREE_SPACE;
	    if (p_info[i].id == FREE_SPACE)
		if (is_extended(ext_info.id))
		    if (p_info[i].first_sector >= ext_info.first_sector &&
			p_info[i].last_sector <= ext_info.last_sector)
			p_info[i].num = LOGICAL;
		    else if (i > 0 &&
			     p_info[i-1].first_sector >=
			     ext_info.first_sector &&
			     p_info[i-1].last_sector <=
			     ext_info.last_sector)
			p_info[i].num = PRI_OR_LOG;
		    else if (i < num_parts-1 &&
			     p_info[i+1].first_sector >=
			     ext_info.first_sector &&
			     p_info[i+1].last_sector <=
			     ext_info.last_sector)
			p_info[i].num = PRI_OR_LOG;
		    else
			p_info[i].num = PRIMARY;
		else /* if (!is_extended(ext_info.id)) */
		    p_info[i].num = PRI_OR_LOG;
	    else /* if (p_info[i].id > 0) */
		while (0); /* Leave these alone */
	}
    }
}

void remove_part(int i)
{
    int p;

    for (p = i; p < num_parts; p++)
	p_info[p] = p_info[p+1];

    num_parts--;
}

void insert_empty_part(int i, int first, int last)
{
    int p;

    for (p = num_parts; p > i; p--)
	 p_info[p] = p_info[p-1];

    p_info[i].first_sector = first;
    p_info[i].last_sector = last;
    p_info[i].offset = 0;
    p_info[i].flags = 0;
    p_info[i].id = FREE_SPACE;
    p_info[i].num = PRI_OR_LOG;
    p_info[i].dos_label[0] = 0;

    num_parts++;
}

void del_part(int i)
{
    int num = p_info[i].num;

    if (i > 0 && (p_info[i-1].id == FREE_SPACE ||
		  p_info[i-1].id == UNUSABLE)) {
	/* Merge with previous partition */
	p_info[i-1].last_sector = p_info[i].last_sector;
	remove_part(i--);
    }

    if (i < num_parts - 1 && (p_info[i+1].id == FREE_SPACE ||
			      p_info[i+1].id == UNUSABLE)) {
	/* Merge with next partition */
	p_info[i+1].first_sector = p_info[i].first_sector;
	remove_part(i);
    }

    if (i > 0)
	p_info[i].first_sector = p_info[i-1].last_sector + 1;
    else
	p_info[i].first_sector = 0;

    if (i < num_parts - 1)
	p_info[i].last_sector = p_info[i+1].first_sector - 1;
    else
	p_info[i].last_sector = sectors*heads*cylinders - 1;

    p_info[i].offset = 0;
    p_info[i].flags = 0;
    p_info[i].id = FREE_SPACE;
    p_info[i].num = PRI_OR_LOG;

    if (IS_LOGICAL(num)) {
	/* We have a logical partition --> shrink the extended partition
	 * if (1) this is the first logical drive, or (2) this is the
	 * last logical drive; and if there are any other logical drives
	 * then renumber the ones after "num".
	 */
	if (i == 0 || (i > 0 && IS_PRIMARY(p_info[i-1].num))) {
	    ext_info.first_sector = p_info[i].last_sector + 1;
	    ext_info.offset = 0;
	}
	if (i == num_parts-1 ||
	    (i < num_parts-1 && IS_PRIMARY(p_info[i+1].num)))
	    ext_info.last_sector = p_info[i].first_sector - 1;
	for (i = 0; i < num_parts; i++)
	    if (p_info[i].num > num)
		p_info[i].num--;
    }

    /* Clean up the rest of the partitions */
    check_part_info();
}

int add_part(int num, int id, int flags, int first, int last, int offset,
	     int want_label)
{
    int i, pri = 0, log = 0;

    if (num_parts == MAXIMUM_PARTS ||
	first < 0 ||
	first >= cylinders*heads*sectors ||
	last < 0 ||
	last >= cylinders*heads*sectors) {
	return -1;		/* bad start or end */
    }

    for (i = 0; i < num_parts; i++)
	if (p_info[i].id > 0 && IS_PRIMARY(p_info[i].num))
	    pri++;
	else if (p_info[i].id > 0 && IS_LOGICAL(p_info[i].num))
	    log++;
    if (is_extended(ext_info.id) && log > 0)
	pri++;

    if (IS_PRIMARY(num))
	if (pri >= 4) {
	    return -1;		/* no room for more */
	} else
	    pri++;

    for (i = 0; i < num_parts && p_info[i].last_sector < first; i++);

    if (i == num_parts || p_info[i].id != FREE_SPACE
	|| last > p_info[i].last_sector) {
	return -1;
    }

    if (is_extended(id)) {
	if (ext_info.id != FREE_SPACE) {
	    return -1;		/* second extended */
	}
	else if (IS_PRIMARY(num)) {
	    ext_info.first_sector = first;
	    ext_info.last_sector = last;
	    ext_info.offset = offset;
	    ext_info.flags = flags;
	    ext_info.id = id;
	    ext_info.num = num;
	    ext_info.dos_label[0] = 0;
	    return 0;
	} else {
	    return -1;		/* explicit extended logical */
	}
    }

    if (IS_LOGICAL(num)) {
	if (!is_extended(ext_info.id)) {
	    print_warning("!!!! Internal error creating logical "
			  "drive with no extended partition !!!!");
	} else {
	    /* We might have a logical partition outside of the extended
	     * partition's range --> we have to extend the extended
	     * partition's range to encompass this new partition, but we
	     * must make sure that there are no primary partitions between
	     * it and the closest logical drive in extended partition.
	     */
	    if (first < ext_info.first_sector) {
		if (i < num_parts-1 && IS_PRIMARY(p_info[i+1].num)) {
		    print_warning(TWO_EXTENDEDS);
		    return -1;
		} else {
		    if (first == 0) {
			ext_info.first_sector = 0;
			ext_info.offset = first = offset;
		    } else {
			ext_info.first_sector = first;
		    }
		}
	    } else if (last > ext_info.last_sector) {
		if (i > 0 && IS_PRIMARY(p_info[i-1].num)) {
		    print_warning(TWO_EXTENDEDS);
		    return -1;
		} else {
		    ext_info.last_sector = last;
		}
	    }
	}
    }

    if (first != p_info[i].first_sector &&
	!(IS_LOGICAL(num) && first == offset)) {
	insert_empty_part(i, p_info[i].first_sector, first-1);
	i++;
    }

    if (last != p_info[i].last_sector)
	insert_empty_part(i+1, last+1, p_info[i].last_sector);

    p_info[i].first_sector = first;
    p_info[i].last_sector = last;
    p_info[i].offset = offset;
    p_info[i].flags = flags;
    p_info[i].id = id;
    p_info[i].num = num;
    p_info[i].dos_label[0] = 0;
    if (want_label && is_dos_partition(id))
	 get_dos_label(i);

    check_part_info();

    return 0;
}

int find_primary(void)
{
    int num = 0, cur = 0;

    while (cur < num_parts && IS_PRIMARY(num))
	if ((p_info[cur].id > 0 && p_info[cur].num == num) ||
	    (is_extended(ext_info.id) && ext_info.num == num)) {
	    num++;
	    cur = 0;
	} else
	    cur++;

    if (!IS_PRIMARY(num))
	return -1;
    else
	return num;
}

int find_logical(int i)
{
    int num = -1;
    int j;

    for (j = i; j < num_parts && num == -1; j++)
	if (p_info[j].id > 0 && IS_LOGICAL(p_info[j].num))
	    num = p_info[j].num;

    if (num == -1) {
	num = 4;
	for (j = 0; j < num_parts; j++)
	    if (p_info[j].id > 0 && p_info[j].num == num)
		num++;
    }

    return num;
}

void inc_logical(int i)
{
    int j;

    for (j = i; j < num_parts; j++)
	if (p_info[j].id > 0 && IS_LOGICAL(p_info[j].num))
	    p_info[j].num++;
}

/* Command menu support by Janne Kukonlehto <jtklehto@phoenix.oulu.fi> September 1994 */

/* Constants for menuType parameter of menuSelect function */
#define MENU_HORIZ 1
#define MENU_VERT 2
#define MENU_ACCEPT_OTHERS 4
#define MENU_BUTTON 8
/* Miscellenous constants */
#define MENU_SPACING 2
#define MENU_MAX_ITEMS 256 /* for simpleMenu function */
#define MENU_UP 1
#define MENU_DOWN 2
#define MENU_RIGHT 3
#define MENU_LEFT 4

struct MenuItem
{
    char key; /* Keyboard shortcut; if zero, then there is no more items in the menu item table */
    char *name; /* Item name, should be eight characters with current implementation */
    char *desc; /* Item description to be printed when item is selected */
};

/* Actual function which prints the button bar and highlights the active button *
 * Should not be called directly. Call function menuSelect instead.             */

int menuUpdate( int y, int x, struct MenuItem *menuItems, int itemLength, char *available, int menuType, int current )
{
    int i, lmargin = x, ymargin = y;
    /* Print available buttons */
    move( y, x ); clrtoeol();
    for( i = 0; menuItems[i].key; i++ )
    {
        char buff[20];
        int lenName;
        /* Search next available button */
        while( menuItems[i].key && !strchr(available, menuItems[i].key) )
        {
            i++;
        }
        if( !menuItems[i].key ) break; /* No more menu items */
        /* If selected item is not available and we have bypassed it, make current item selected */
        if( current < i && menuItems[current].key < 0 ) current = i;
        /* If current item is selected, highlight it */
        if( current == i ) /*attron( A_REVERSE )*/ standout ();
        /* Print item */
        lenName = strlen( menuItems[i].name );
        if(lenName > itemLength)
            print_warning("Menu item too long. Menu may look odd.");
        if( menuType & MENU_BUTTON )
            sprintf( buff, "[%*s%-*s]", (itemLength - lenName) / 2, "",
                (itemLength - lenName + 1) / 2 + lenName, menuItems[i].name );
        else
            sprintf( buff, "%*s%-*s", (itemLength - lenName) / 2, "",
                (itemLength - lenName + 1) / 2 + lenName, menuItems[i].name );
        mvaddstr( y, x, buff );
        /* Lowlight after selected item */
        if( current == i ) /*attroff( A_REVERSE )*/ standend ();
        /* Calculate position for the next item */
        if( menuType & MENU_VERT )
        {
            y += 1;
            if( y >= WARNING_START )
            {
                y = ymargin;
                x += itemLength + MENU_SPACING;
                if( menuType & MENU_BUTTON ) x += 2;
            }
        }
        else
        {
            x += itemLength + MENU_SPACING;
            if( menuType & MENU_BUTTON ) x += 2;
            if( x > COLUMNS - lmargin - 12 )
            {
                x = lmargin;
                y ++ ;
            }
        }
    }
    /* Print the description of selected item */
    mvaddstr( WARNING_START + 1, (COLUMNS - strlen( menuItems[current].desc )) / 2, menuItems[current].desc );
    return y;
}

/* This function takes a list of menu items, lets the user choose one of them *
 * and returns the value keyboard shortcut of the selected menu item          */

int menuSelect( int y, int x, struct MenuItem *menuItems, int itemLength, char *available, int menuType, int menuDefault )
{
    int i, ylast = y, key = 0, current = menuDefault;
    if( !( menuType & ( MENU_HORIZ | MENU_VERT ) ) )	
    {
        print_warning("Menu without direction. Defaulting horizontal.");
        menuType |= MENU_HORIZ;
    }
    /* Make sure that the current is one of the available items */
    while( !strchr(available, menuItems[current].key) )
    {
        current ++ ;
        if( !menuItems[current].key ) current = 0;
    }
    /* Repeat until allowable choice has been made */
    while( !key )
    {
        /* Display the menu */
        ylast = menuUpdate( y, x, menuItems, itemLength, available, menuType, current );
        refresh();
        key = getch();
        /* Clear out all prompts and such */
        clear_warning();
        for( i = y; i < ylast; i ++ )
        {
            move( i, x );
            clrtoeol();
        }
        move( WARNING_START + 1, 0 );
        clrtoeol();
        /* Cursor keys */
        if( key == ESC )
        {
            /* Check whether this is a real ESC or one of extended keys */
            /*nodelay(stdscr, TRUE);*/
            key = getch();
            /*nodelay(stdscr, FALSE);*/
            if( key == /*ERR*/ ESC )
            {
                /* This is a real ESC */
                key = ESC;
            }
            if( key == '[' )
            {
                /* This is one extended keys */
                switch( getch() )
                {
                    case 'A': /* Up arrow */
                        if( menuType & MENU_VERT )
                        {
                            do {
                                current -- ;
                                if( current < 0 ) while( menuItems[current+1].key ) current ++ ;
                            } while( !strchr( available, menuItems[current].key ) );
                            key = 0;
                        }
                        else
                            key = MENU_UP;
                        break;
                    case 'B': /* Down arrow */
                        if( menuType & MENU_VERT )
                        {
                            do {
                                current ++ ;
                                if( !menuItems[current].key ) current = 0 ;
                            } while( !strchr( available, menuItems[current].key ) );
                            key = 0;
                        }
                        else
                            key = MENU_DOWN;
                        break;
                    case 'C': /* Right arrow */
                        if( menuType & MENU_HORIZ )
                        {
                            do {
                                current ++ ;
                                if( !menuItems[current].key ) 
                                {
                                    current = 0 ;
                                }
                            } while( !strchr( available, menuItems[current].key ) );
                            key = 0;
                        }
                        else
                            key = MENU_RIGHT;
                        break;
                    case 'D': /* Left arrow */
                        if( menuType & MENU_HORIZ )
                        {
                            do {
                                current -- ;
                                if( current < 0 )
                                {
                                    while( menuItems[current + 1].key ) current ++ ;
                                }
                            } while( !strchr( available, menuItems[current].key ) );
                            key = 0;
                        }
                        else
                            key = MENU_LEFT;
                        break;
                }
            }
        }
        /* Enter equals to the keyboard shortcut of current menu item */
        if( key == 13 )
        {
            key = menuItems[current].key;
        }
        /* Should all keys to be accepted? */
        if( key && (menuType & MENU_ACCEPT_OTHERS) ) break;
        /* Is pressed key among acceptable ones */
        if( key && (strchr(available, tolower(key)) || strchr(available, key)) ) break;
        /* The key has not been accepted so far -> let's reject it */
        if( key )
        {
            key = 0;
            putchar( BELL );
            print_warning("Illegal key");
        }
    }
    /* Clear out prompts and such */
    clear_warning();
    for( i = y; i <= ylast; i ++ )
    {
        move( i, x );
        clrtoeol();
    }
    move( WARNING_START + 1, 0 );
    clrtoeol();
    return key;
}

/* A function which displays "Press a key to continue" and waits for a keypress *
 * Perhaps calling function menuSelect is a bit overkill but who cares?         */

void menuContinue(void)
{
    static struct MenuItem menuContinueBtn[]=
    {
        { 'c', "", "Press a key to continue" },
        { 0, NULL, NULL }
    };

    menuSelect(COMMAND_LINE_Y, COMMAND_LINE_X,
	menuContinueBtn, 0, "c", MENU_HORIZ | MENU_ACCEPT_OTHERS, 0 );
}

/* Function menuSelect takes way too many parameters  *
 * Luckily, most of time we can do with this function */

int menuSimple(struct MenuItem *menuItems, int menuDefault)
{
    int i, j, itemLength = 0;
    char available[MENU_MAX_ITEMS];
    for(i = 0; menuItems[i].key; i++)
    {
        j = strlen( menuItems[i].name );
        if( j > itemLength ) itemLength = j;
        available[i] = menuItems[i].key;
    }
    available[i] = 0;
    return menuSelect(COMMAND_LINE_Y, COMMAND_LINE_X, menuItems, itemLength,
        available, MENU_HORIZ | MENU_BUTTON, menuDefault);
}

/* End of command menu support code */

void new_part(int i)
{
    char response[LINE_LENGTH], def[LINE_LENGTH];
    char c;
    int first = p_info[i].first_sector;
    int last = p_info[i].last_sector;
    int offset = 0;
    int flags = 0;
    int id = LINUX;
    int num = -1;
    int num_sects = last - first + 1;
    int len, ext, j;

    if (p_info[i].num == PRI_OR_LOG) {
        static struct MenuItem menuPartType[]=
        {
            { 'p', "Primary", "Create a new primary partition" },
            { 'l', "Logical", "Create a new logical partition" },
            { ESC, "Cancel", "Don't create a partition" },
            { 0, NULL, NULL }
        };
        
	c = menuSimple( menuPartType, 0 );
	if (toupper(c) == 'P')
	    num = find_primary();
	else if (toupper(c) == 'L')
	    num = find_logical(i);
	else
	    return;
    } else if (p_info[i].num == PRIMARY)
	num = find_primary();
    else if (p_info[i].num == LOGICAL)
	num = find_logical(i);
    else
	print_warning("!!! Internal error !!!");

    sprintf(def, "%.2f", ceiling(num_sects/20.48)/100);
    mvaddstr(COMMAND_LINE_Y, COMMAND_LINE_X, "Size (in MB): ");
    if ((len = get_string(response, LINE_LENGTH, def)) <= 0 &&
	len != GS_DEFAULT)
	return;
    else if (len > 0) {
#define num_cyls(bytes) (round_int(bytes/SECTOR_SIZE/(sectors*heads)))
	for (j = 0;
	     j < len-1 && (isdigit(response[j]) || response[j] == '.');
	     j++);
	if (toupper(response[j]) == 'K') {
	    num_sects = num_cyls(atof(response)*1024)*sectors*heads;
	} else if (toupper(response[j]) == 'M') {
	    num_sects = num_cyls(atof(response)*1024*1024)*sectors*heads;
	} else if (toupper(response[j]) == 'C') {
	    num_sects = round_int(atof(response))*sectors*heads;
	} else if (toupper(response[j]) == 'S') {
	    num_sects = round_int(atof(response));
	} else {
	    num_sects = num_cyls(atof(response)*1024*1024)*sectors*heads;
	}
    }

    if (num_sects <= 0 ||
	num_sects > p_info[i].last_sector - p_info[i].first_sector + 1)
	return;

    move( COMMAND_LINE_Y, COMMAND_LINE_X ); clrtoeol();
    if (num_sects < p_info[i].last_sector - p_info[i].first_sector + 1) {
	/* Determine where inside free space to put partition.
	 */
	static struct MenuItem menuPlace[]=
	{
	    { 'b', "Beginning", "Add partition at beginning of free space" },
	    { 'e', "End", "Add partition at end of free space" },
	    { ESC, "Cancel", "Don't create a partition" },
	    { 0, NULL, NULL }
	};
	c = menuSimple( menuPlace, 0 );
	if (toupper(c) == 'B')
	    last = first + num_sects - 1;
	else if (toupper(c) == 'E')
	    first = last - num_sects + 1;
	else
	    return;
    }

    if (IS_LOGICAL(num) && !is_extended(ext_info.id)) {
	/* We want to add a logical partition, but need to create an
	 * extended partition first.
	 */
	if ((ext = find_primary()) < 0) {
	    print_warning(NEED_EXT);
	    return;
	}
	(void) add_part(ext, DOS_EXTENDED, 0, first, last,
		       (first == 0 ? sectors : 0), 0);
	first = ext_info.first_sector + ext_info.offset;
    }

    if (IS_LOGICAL(num))
	inc_logical(i);

    /* Now we have a complete partition to ourselves */
    if (first == 0 || IS_LOGICAL(num))
	offset = sectors;

    (void) add_part(num, id, flags, first, last, offset, 0);
}

void clear_p_info(void)
{
    num_parts = 1;
    p_info[0].first_sector = 0;
    p_info[0].last_sector = sectors*heads*cylinders - 1;
    p_info[0].offset = 0;
    p_info[0].flags = 0;
    p_info[0].id = FREE_SPACE;
    p_info[0].num = PRI_OR_LOG;

    ext_info.first_sector = 0;
    ext_info.last_sector = 0;
    ext_info.offset = 0;
    ext_info.flags = 0;
    ext_info.id = FREE_SPACE;
    ext_info.num = PRIMARY;
}

void fill_p_info(void)
{
    int p, i;
    struct hd_geometry geometry;
    partition_table buffer;
    partition_info tmp_ext = { 0, 0, 0, 0, FREE_SPACE, PRIMARY };

    if ((fd = open(disk_device, O_RDWR)) < 0) {
	 if ((fd = open(disk_device, O_RDONLY)) < 0)
	      fatal(BAD_OPEN);
	 opentype = O_RDONLY;
	 print_warning(READONLY_WARN);
	 if (curses_started) {
	      refresh();
	      getch();
	      clear_warning();
	 }
    } else
	 opentype = O_RDWR;
    opened = TRUE;

    read_sector(buffer.c.b, 0);

    if (!ioctl(fd, HDIO_GETGEO, &geometry)) {
	if (!heads)
	    heads = geometry.heads;
	if (!sectors)
	    sectors = geometry.sectors;
	if (!cylinders)
	    cylinders = geometry.cylinders;
    }

    if (!heads || !sectors || !cylinders)
	fatal(BAD_GEOMETRY);

    clear_p_info();

    if (!zero_table) {
	for (i = 0; i < 4; i++) {
	    int bs = buffer.p.part[i].start_sect;

	    if (buffer.p.part[i].sys_ind > 0 &&
		add_part(i,
			 buffer.p.part[i].sys_ind,
			 buffer.p.part[i].boot_ind,
			 ((bs <= sectors) ? 0 : bs),
			 buffer.p.part[i].start_sect +
			 buffer.p.part[i].nr_sects - 1,
			 ((bs <= sectors) ? bs : 0),
			 1)) {
		fatal(BAD_PRIMARY);
	    }
	    if (is_extended(buffer.p.part[i].sys_ind))
		tmp_ext = ext_info;
	}

	if (is_extended(tmp_ext.id)) {
	    ext_info = tmp_ext;
	    logical_sectors[logical] =
		 ext_info.first_sector + ext_info.offset;
	    read_sector(buffer.c.b, logical_sectors[logical++]);
	    i = 4;
	    do {
		for (p = 0;
		     p < 4 && (!buffer.p.part[p].sys_ind ||
			       is_extended(buffer.p.part[p].sys_ind));
		     p++);

		if (p < 4 && add_part(i++,
			     buffer.p.part[p].sys_ind,
			     buffer.p.part[p].boot_ind,
			     logical_sectors[logical-1],
			     logical_sectors[logical-1] +
			     buffer.p.part[p].start_sect +
			     buffer.p.part[p].nr_sects - 1,
			     buffer.p.part[p].start_sect,
			     1)) {
		    fatal(BAD_LOGICAL);
		}

		for (p = 0;
		     p < 4 && !is_extended(buffer.p.part[p].sys_ind);
		     p++);
		if (p < 4) {
		    logical_sectors[logical] = ext_info.first_sector
			 + ext_info.offset + buffer.p.part[p].start_sect;
		    read_sector(buffer.c.b, logical_sectors[logical++]);
		}
	    } while (p < 4 && logical < MAXIMUM_PARTS-4);
	}
    }
}

void fill_part_table(struct partition *p, partition_info *pi)
{
    int sects;

    p->boot_ind = pi->flags;
    p->sys_ind = pi->id;
    if (IS_LOGICAL(pi->num))
	p->start_sect = pi->offset;
    else
	p->start_sect = pi->first_sector + pi->offset;
    p->nr_sects = pi->last_sector - (pi->first_sector+pi->offset) + 1;
    sects = (((pi->first_sector+pi->offset)/(sectors*heads) > 1023) ?
	     heads*sectors*1024 - 1 : pi->first_sector+pi->offset);
    set_hsc(p->head, p->sector, p->cyl, sects);
    sects = ((pi->last_sector/(sectors*heads) > 1023) ?
	     heads*sectors*1024 - 1 : pi->last_sector);
    set_hsc(p->end_head, p->end_sector, p->end_cyl, sects);
}

void fill_primary_table(partition_table *buffer)
{
    int i;

    /* Zero out existing table */
    for (i = 0x1BE; i < SECTOR_SIZE; i++)
	buffer->c.b[i] = 0;

    for (i = 0; i < num_parts; i++)
	if (IS_PRIMARY(p_info[i].num))
	    fill_part_table(&(buffer->p.part[p_info[i].num]), &(p_info[i]));

    if (is_extended(ext_info.id))
	fill_part_table(&(buffer->p.part[ext_info.num]), &ext_info);

    buffer->p.flag = PART_TABLE_FLAG;
}

void fill_logical_table(partition_table *buffer, partition_info *pi)
{
    struct partition *p;
    int i, sects;

    for (i = 0; i < logical && pi->first_sector != logical_sectors[i]; i++);
    if (i == logical || buffer->p.flag != (unsigned short)PART_TABLE_FLAG)
	for (i = 0; i < SECTOR_SIZE; i++)
	    buffer->c.b[i] = 0;

    /* Zero out existing table */
    for (i = 0x1BE; i < SECTOR_SIZE; i++)
	buffer->c.b[i] = 0;

    fill_part_table(&(buffer->p.part[0]), pi);

    for (i = 0;
	 i < num_parts && pi->num != p_info[i].num - 1;
	 i++);

    if (i < num_parts) {
	p = &(buffer->p.part[1]);
	pi = &(p_info[i]);

	p->boot_ind = 0;
	p->sys_ind = DOS_EXTENDED;
	p->start_sect = pi->first_sector - ext_info.first_sector - ext_info.offset;
	p->nr_sects = pi->last_sector - pi->first_sector + 1;
	sects = ((pi->first_sector/(sectors*heads) > 1023) ?
		 heads*sectors*1024 - 1 : pi->first_sector);
	set_hsc(p->head, p->sector, p->cyl, sects);
	sects = ((pi->last_sector/(sectors*heads) > 1023) ?
		 heads*sectors*1024 - 1 : pi->last_sector);
	set_hsc(p->end_head, p->end_sector, p->end_cyl, sects);
    }

    buffer->p.flag = PART_TABLE_FLAG;
}

void write_part_table(void)
{
    int i, done = FALSE, len;
    partition_table buffer;
    struct stat s;
    int is_bdev;
    char response[LINE_LENGTH];

    if (opentype == O_RDONLY) {
	 print_warning(READONLY_WARN);
	 refresh();
	 getch();
	 clear_warning();
	 return;
    }

    is_bdev = 0;
    if(fstat(fd, &s) == 0 && S_ISBLK(s.st_mode))
	 is_bdev = 1;

    if (is_bdev) {
	 print_warning(WRITE_WARN);

	 while (!done) {
	      mvaddstr(COMMAND_LINE_Y, COMMAND_LINE_X,
		       "Are you sure you want write the partition table "
		       "to disk? (yes or no): ");
	      len = get_string(response, LINE_LENGTH, NULL);
	      clear_warning();
	      if (len == GS_ESCAPE)
		   return;
	      else if (len == 2 &&
		       toupper(response[0]) == 'N' &&
		       toupper(response[1]) == 'O') {
		   print_warning(NO_WRITE);
		   return;
	      } else if (len == 3 &&
			 toupper(response[0]) == 'Y' &&
			 toupper(response[1]) == 'E' &&
			 toupper(response[2]) == 'S')
		   done = TRUE;
	      else
		   print_warning(YES_NO);
	 }

	 clear_warning();
	 print_warning(WRITING_PART);
	 refresh();
    }

    read_sector(buffer.c.b, 0);
    fill_primary_table(&buffer);
    write_sector(buffer.c.b, 0);

    for (i = 0; i < num_parts; i++)
	if (IS_LOGICAL(p_info[i].num)) {
	    read_sector(buffer.c.b, p_info[i].first_sector);
	    fill_logical_table(&buffer, &(p_info[i]));
	    write_sector(buffer.c.b, p_info[i].first_sector);
	}

    if (is_bdev) {
	 sync();
	 sleep(2);
	 if (!ioctl(fd,BLKRRPART))
	      changed = TRUE;
	 sync();
	 sleep(4);

	 clear_warning();
	 if (changed)
	      print_warning(YES_WRITE);
	 else
	      print_warning(RRPART_FAILED);
    } else
	 print_warning(YES_WRITE);
}

void fp_printf(FILE *fp, char *format, ...)
{
    va_list args;
    char buf[1024];
    int y, x;

    va_start(args, format);
    vsprintf(buf, format, args);
    va_end(args);

    if (fp == NULL) {
	/* The following works best if the string to be printed has at
           most only one newline. */
	printw("%s", buf);
	getyx(stdscr, y, x);
	if (y >= COMMAND_LINE_Y-2) {
	    menuContinue();
	    erase();
	    move(0, 0);
	}
    } else
	fprintf(fp, "%s", buf);
}

#define MAX_PER_LINE 16
void print_file_buffer(FILE *fp, char *buffer)
{
    int i,l;

    for (i = 0, l = 0; i < SECTOR_SIZE; i++, l++) {
	if (l == 0)
	    fp_printf(fp, "0x%03X:", i);
	fp_printf(fp, " %02X", (unsigned char) buffer[i]);
	if (l == MAX_PER_LINE - 1) {
	    fp_printf(fp, "\n");
	    l = -1;
	}
    }
    if (l > 0)
	fp_printf(fp, "\n");
    fp_printf(fp, "\n");
}

void print_raw_table(void)
{
    int i, to_file;
    partition_table buffer;
    char fname[LINE_LENGTH];
    FILE *fp;

    if (print_only) {
	fp = stdout;
	to_file = TRUE;
    } else {
	mvaddstr(COMMAND_LINE_Y, COMMAND_LINE_X,
		 "Enter filename or press RETURN to display on screen: ");

	if ((to_file = get_string(fname, LINE_LENGTH, NULL)) < 0)
	    return;

	if (to_file) {
	    if ((fp = fopen(fname, "w")) == NULL) {
		char errstr[LINE_LENGTH];
		sprintf(errstr, PRINT_OPEN_ERR, fname);
		print_warning(errstr);
		return;
	    }
	} else {
	    fp = NULL;
	    erase();
	    move(0, 0);
	}
    }

    fp_printf(fp, "Disk Drive: %s\n", disk_device);

    fp_printf(fp, "Sector 0:\n");
    read_sector(buffer.c.b, 0);
    fill_primary_table(&buffer);
    print_file_buffer(fp, buffer.c.b);

    for (i = 0; i < num_parts; i++)
	if (IS_LOGICAL(p_info[i].num)) {
	    fp_printf(fp, "Sector %d:\n", p_info[i].first_sector);
	    read_sector(buffer.c.b, p_info[i].first_sector);
	    fill_logical_table(&buffer, &(p_info[i]));
	    print_file_buffer(fp, buffer.c.b);
	}

    if (to_file) {
	if (!print_only)
	    fclose(fp);
    } else {
        menuContinue();
    }
}

void print_p_info_entry(FILE *fp, partition_info *p)
{
    int size;
    char part_str[40];

    if (p->id == UNUSABLE)
	fp_printf(fp, "   None   ");
    else if (p->id == FREE_SPACE && p->num == PRI_OR_LOG)
	fp_printf(fp, "   Pri/Log");
    else if (p->id == FREE_SPACE && p->num == PRIMARY)
	fp_printf(fp, "   Primary");
    else if (p->id == FREE_SPACE && p->num == LOGICAL)
	fp_printf(fp, "   Logical");
    else
	fp_printf(fp, "%2d %-7.7s", p->num+1,
		  IS_LOGICAL(p->num) ? "Logical" : "Primary");

    fp_printf(fp, " ");

    fp_printf(fp, "%8d%c", p->first_sector,
	      ((p->first_sector/(sectors*heads)) !=
	       ((float)p->first_sector/(sectors*heads)) ?
	       '*' : ' '));

    fp_printf(fp, "%8d%c", p->last_sector,
	      (((p->last_sector+1)/(sectors*heads)) !=
	       ((float)(p->last_sector+1)/(sectors*heads)) ?
	       '*' : ' '));

    fp_printf(fp, "%7d%c", p->offset,
	      ((((p->first_sector == 0 || IS_LOGICAL(p->num)) &&
		 (p->offset != sectors)) ||
		(p->first_sector != 0 && IS_PRIMARY(p->num) &&
		 p->offset != 0)) ?
	       '#' : ' '));

    size = p->last_sector - p->first_sector + 1;
    fp_printf(fp, "%8d%c", size,
	      ((size/(sectors*heads)) != ((float)size/(sectors*heads)) ?
	       '*' : ' '));

    fp_printf(fp, " ");

    if (p->id == UNUSABLE)
	sprintf(part_str, "%.17s", "Unusable");
    else if (p->id == FREE_SPACE)
	sprintf(part_str, "%.17s", "Free Space");
    else if (partition_type[p->id])
	sprintf(part_str, "%.17s (%02X)", partition_type[p->id], p->id);
    else
	sprintf(part_str, "%.17s (%02X)", "Unknown", p->id);
    fp_printf(fp, "%-22.22s", part_str);

    fp_printf(fp, " ");

    if (p->flags == ACTIVE_FLAG)
	fp_printf(fp, "Boot (%02X)", p->flags);
    else if (p->flags != 0)
	fp_printf(fp, "Unknown (%02X)", p->flags);
    else
	fp_printf(fp, "None (%02X)", p->flags);

    fp_printf(fp, "\n");
}

void print_p_info(void)
{
    char fname[LINE_LENGTH];
    FILE *fp;
    int i, to_file, pext = is_extended(ext_info.id);

    if (print_only) {
	fp = stdout;
	to_file = TRUE;
    } else {
	mvaddstr(COMMAND_LINE_Y, COMMAND_LINE_X,
		 "Enter filename or press RETURN to display on screen: ");

	if ((to_file = get_string(fname, LINE_LENGTH, NULL)) < 0)
	    return;

	if (to_file) {
	    if ((fp = fopen(fname, "w")) == NULL) {
		char errstr[LINE_LENGTH];
		sprintf(errstr, PRINT_OPEN_ERR, fname);
		print_warning(errstr);
		return;
	    }
	} else {
	    fp = NULL;
	    erase();
	    move(0, 0);
	}
    }

    fp_printf(fp, "Partition Table for %s\n", disk_device);
    fp_printf(fp, "\n");
    fp_printf(fp, "            First    Last\n");
    fp_printf(fp, " # Type     Sector   Sector   Offset  Length   Filesystem Type (ID)   Flags\n");
    fp_printf(fp, "-- ------- -------- --------- ------ --------- ---------------------- ---------\n");

    for (i = 0; i < num_parts; i++) {
	if (pext && (p_info[i].first_sector >= ext_info.first_sector)) {
	    print_p_info_entry(fp,&ext_info);
	    pext = FALSE;
	}
	print_p_info_entry(fp, &(p_info[i]));
    }

    if (to_file) {
	if (!print_only)
	    fclose(fp);
    } else {
        menuContinue();
    }
}

void print_part_entry(FILE *fp, int num, partition_info *pi)
{
    int first = 0, start = 0, end = 0, size = 0;
    int ss = 0, sh = 0, sc = 0;
    int es = 0, eh = 0, ec = 0;
    int flags = 0, id = 0;

    if (pi != NULL) {
	flags = pi->flags;
	id = pi->id;

	if (IS_LOGICAL(num))
	    first = pi->offset;
	else
	    first = pi->first_sector + pi->offset;

	start = pi->first_sector + pi->offset;
	end = pi->last_sector;
	size = end - start + 1;
	if ((start/(sectors*heads)) > 1023)
	    start = heads*sectors*1024 - 1;
	if ((end/(sectors*heads)) > 1023)
	    end = heads*sectors*1024 - 1;

	ss = start % sectors + 1;
	start /= sectors;
	sh = start % heads;
	sc = start / heads;

	es = end % sectors + 1;
	end /= sectors;
	eh = end % heads;
	ec = end / heads;
    }

    fp_printf(fp, "%2d  0x%02X %4d %4d %4d 0x%02X %4d %4d %4d %7d %9d\n",
	      num+1, flags, sh, ss, sc, id, eh, es, ec, first, size);
}


void print_part_table(void)
{
    int i, j, to_file;
    char fname[LINE_LENGTH];
    FILE *fp;

    if (print_only) {
	fp = stdout;
	to_file = TRUE;
    } else {
	mvaddstr(COMMAND_LINE_Y, COMMAND_LINE_X,
		 "Enter filename or press RETURN to display on screen: ");

	if ((to_file = get_string(fname, LINE_LENGTH, NULL)) < 0)
	    return;

	if (to_file) {
	    if ((fp = fopen(fname, "w")) == NULL) {
		char errstr[LINE_LENGTH];
		sprintf(errstr, PRINT_OPEN_ERR, fname);
		print_warning(errstr);
		return;
	    }
	} else {
	    fp = NULL;
	    erase();
	    move(0, 0);
	}
    }

    fp_printf(fp, "Partition Table for %s\n", disk_device);
    fp_printf(fp, "\n");
    fp_printf(fp, "         ---Starting---      ----Ending----   Start Number of\n");
    fp_printf(fp, " # Flags Head Sect Cyl   ID  Head Sect Cyl   Sector  Sectors\n");
    fp_printf(fp, "-- ----- ---- ---- ---- ---- ---- ---- ---- ------- ---------\n");

    for (i = 0; i < 4; i++) {
	for (j = 0;
	     j < num_parts && (p_info[j].id <= 0 || p_info[j].num != i);
	     j++);
	if (j < num_parts) {
	    print_part_entry(fp, i, &(p_info[j]));
	} else if (is_extended(ext_info.id) && ext_info.num == i) {
	    print_part_entry(fp, i, &ext_info);
	} else {
	    print_part_entry(fp, i, NULL);
	}
    }

    for (i = 0; i < num_parts; i++)
	if (IS_LOGICAL(p_info[i].num))
	    print_part_entry(fp, p_info[i].num, &(p_info[i]));

    if (to_file) {
	if (!print_only)
	    fclose(fp);
    } else {
        menuContinue();
    }
}

void print_tables(void)
{
    int done = FALSE;

    static struct MenuItem menuFormat[]=
    {
        { 'r', "Raw", "Print the table using raw data format" },
        { 's', "Sectors", "Print the table ordered by sectors" },
        { 't', "Table", "Just print the partition table" },
        { ESC, "Cancel", "Don't print the table" },
        { 0, NULL, NULL }
    };
    
    while (!done)
	switch ( toupper(menuSimple( menuFormat, 2)) ) {
	case 'R':
	    print_raw_table();
	    done = TRUE;
	    break;
	case 'S':
	    print_p_info();
	    done = TRUE;
	    break;
	case 'T':
	    print_part_table();
	    done = TRUE;
	    break;
	case ESC:
	    done = TRUE;
	    break;
	}
}

#define END_OF_HELP "EOHS!"
#define NEW_HELP_SCREEN "SNHS!"
void display_help()
{
    char *help_text[] = {
	"Help Screen for cfdisk " VERSION,
	"",
	"This is cfdisk, a curses based disk partitioning programs, which",
	"allows you to create, delete and modify partitions on your hard",
	"disk drive.",
	"",
	"Copyright (C) 1994-1997 Kevin E. Martin & aeb",
	"",
	"Command      Meaning",
	"-------      -------",
	"  b          Toggle bootable flag of the current partition",
	"  d          Delete the current partition",
	"  g          Change cylinders, heads, sectors-per-track parameters",
	"             WARNING: This option should only be used by people who",
	"             know what they are doing.",
	"  h          Print this screen",
	"  m          Maximize disk usage of the current partition",
	"             Note: This may make the partition incompatible with",
	"             DOS, OS/2, ...",
	"  n          Create new partition from free space",
	"  p          Print partition table to the screen or to a file",
	"             There are several different formats for the partition",
	"             that you can choose from:",
	"                r - Raw data (exactly what would be written to disk)",
	"                s - Table ordered by sectors",
	"                t - Table in raw format",
	"  q          Quit program without writing partition table",
	"  t          Change the filesystem type",
	"  u          Change units of the partition size display",
	"             Rotates through Mb, sectors and cylinders",
	"  W          Write partition table to disk (must enter upper case W)",
        "             Since this might destroy data on the disk, you must",
	"             either confirm or deny the write by entering `yes' or",
	"             `no'",
	"Up Arrow     Move cursor to the previous partition",
	"Down Arrow   Move cursor to the next partition",
	"CTRL-L       Redraws the screen",
	"  ?          Print this screen",
	"",
	"Note: All of the commands can be entered with either upper or lower",
	"case letters (except for Writes).",
	END_OF_HELP
    };

    int cur_line = 0;
    FILE *fp = NULL;

    erase();
    move(0, 0);
    while (strcmp(help_text[cur_line], END_OF_HELP))
	if (!strcmp(help_text[cur_line], NEW_HELP_SCREEN)) {
	    menuContinue();
	    erase();
	    move(0, 0);
	    cur_line++;
	} else
	    fp_printf(fp, "%s\n", help_text[cur_line++]);

    menuContinue();
}

int change_geometry(void)
{
    int ret_val = FALSE;
    int done = FALSE;
    char def[LINE_LENGTH];
    char response[LINE_LENGTH];
    int tmp_val;

    while (!done) {
        static struct MenuItem menuGeometry[]=
        {
            { 'c', "Cylinders", "Change cylinder geometry" },
            { 'h', "Heads", "Change head geometry" },
            { 's', "Sectors", "Change sector geometry" },
            { 'd', "Done", "Done with changing geometry" },
            { 0, NULL, NULL }
        };
	move(COMMAND_LINE_Y, COMMAND_LINE_X);
	clrtoeol();
	refresh();

	clear_warning();

	switch (toupper( menuSimple(menuGeometry, 3) )) {
	case 'C':
	    sprintf(def, "%d", cylinders);
	    mvaddstr(COMMAND_LINE_Y, COMMAND_LINE_X,
		     "Enter the number of cylinders: ");
	    if (get_string(response, LINE_LENGTH, def) > 0) {
		tmp_val = atoi(response);
		if (tmp_val > 0 && tmp_val <= MAX_CYLINDERS) {
		    cylinders = tmp_val;
		    ret_val = TRUE;
		} else
		    print_warning(BAD_CYLINDERS);
	    }
	    break;
	case 'H':
	    sprintf(def, "%d", heads);
	    mvaddstr(COMMAND_LINE_Y, COMMAND_LINE_X,
		     "Enter the number of heads: ");
	    if (get_string(response, LINE_LENGTH, def) > 0) {
		tmp_val = atoi(response);
		if (tmp_val > 0 && tmp_val <= MAX_HEADS) {
		    heads = tmp_val;
		    ret_val = TRUE;
		} else
		    print_warning(BAD_HEADS);
	    }
	    break;
	case 'S':
	    sprintf(def, "%d", sectors);
	    mvaddstr(COMMAND_LINE_Y, COMMAND_LINE_X,
		     "Enter the number of sectors per track: ");
	    if (get_string(response, LINE_LENGTH, def) > 0) {
		tmp_val = atoi(response);
		if (tmp_val > 0 && tmp_val <= MAX_SECTORS) {
		    sectors = tmp_val;
		    ret_val = TRUE;
		} else
		    print_warning(BAD_SECTORS);
	    }
	    break;
	case ESC:
	case 'D':
	    done = TRUE;
	    break;
	default:
	    putchar(BELL);
	    break;
	}
    }

    if (ret_val) {
	int disk_end = heads*sectors*cylinders-1;

	if (p_info[num_parts-1].last_sector > disk_end) {
	    while (p_info[num_parts-1].first_sector > disk_end) {
		if (p_info[num_parts-1].id == FREE_SPACE ||
		    p_info[num_parts-1].id == UNUSABLE)
		    remove_part(num_parts-1);
		else
		    del_part(num_parts-1);
	    }

	    p_info[num_parts-1].last_sector = disk_end;

	    if (ext_info.last_sector > disk_end)
		ext_info.last_sector = disk_end;
	} else if (p_info[num_parts-1].last_sector < disk_end) {
	    if (p_info[num_parts-1].id == FREE_SPACE ||
		p_info[num_parts-1].id == UNUSABLE) {
		p_info[num_parts-1].last_sector = disk_end;
	    } else {
		insert_empty_part(num_parts,
				  p_info[num_parts-1].last_sector+1,
				  disk_end);
	    }
	}

	/* Make sure the partitions are correct */
	check_part_info();
    }

    return ret_val;
}

void change_id(int i)
{
    char id[LINE_LENGTH], def[LINE_LENGTH];
    int num_types = 0;
    int num_across, num_down;
    int len, new_id = LINUX;
    int y_start, y_end;
    int j, pos;

    for (num_types = 0, j = 1; j < NUM_PART_TYPES; j++)
	if (partition_type[j])
	    num_types++;

    num_across = COLS/COL_ID_WIDTH;
    num_down = (((float)num_types)/num_across + 1);
    y_start = COMMAND_LINE_Y - 1 - num_down;
    if (y_start > DISK_TABLE_START+cur_part+4)
	y_start = DISK_TABLE_START+cur_part+4;
    y_end = y_start + num_down - 1;

    for (j = y_start - 1; j <= y_end + 1; j++) {
	move(j, 0);
	clrtoeol();
    }

    for (pos = 0, j = 1; j < NUM_PART_TYPES; j++)
	if (partition_type[j]) {
	    move(y_start + pos % num_down, (pos/num_down)*COL_ID_WIDTH + 1);
	    printw("%02X %-16.16s", j, partition_type[j]);
	    pos++;
	}

    sprintf(def, "%02X", new_id);
    mvaddstr(COMMAND_LINE_Y, COMMAND_LINE_X, "Enter filesystem type: ");
    if ((len = get_string(id, 2, def)) <= 0 && len != GS_DEFAULT)
	return;

    if (len != GS_DEFAULT) {
	if (!isxdigit(id[0]))
	    return;
	new_id = (isdigit(id[0]) ? id[0] - '0' : tolower(id[0]) - 'a' + 10);
	if (len == 2)
	    if (isxdigit(id[1]))
		new_id = new_id*16 +
		    (isdigit(id[1]) ? id[1] - '0' : tolower(id[1]) - 'a' + 10);
	    else
		return;
    }

    if (new_id == 0)
	print_warning(ID_EMPTY);
    else if (is_extended(new_id))
	print_warning(ID_EXT);
    else
	p_info[i].id = new_id;
}

void draw_partition(int i)
{
    int size, j;
    int y = i + DISK_TABLE_START + 2 - (cur_part/NUM_ON_SCREEN)*NUM_ON_SCREEN;

    if (!arrow_cursor) {
	move(y, 0);
	for (j = 0; j < COLS; j++)
	    addch(' ');
    }

    if (p_info[i].id > 0) {
	mvprintw(y, NAME_START,
		 "%s%d", disk_device, p_info[i].num+1);
	if (p_info[i].flags) {
	    if (p_info[i].flags == ACTIVE_FLAG)
		mvaddstr(y, FLAGS_START, "Boot");
	    else
		mvprintw(y, FLAGS_START, "Unk(%02X)", p_info[i].flags);
	    if (p_info[i].first_sector == 0 || IS_LOGICAL(p_info[i].num)) {
		if (p_info[i].offset != sectors)
		    addstr(", NC");
	    } else {
		if (p_info[i].offset != 0)
		    addstr(", NC");
	    }
	} else {
	    if (p_info[i].first_sector == 0 || IS_LOGICAL(p_info[i].num)) {
		if (p_info[i].offset != sectors)
		    mvaddstr(y, FLAGS_START, "NC");
	    } else {
		if (p_info[i].offset != 0)
		    mvaddstr(y, FLAGS_START, "NC");
	    }
	}
    }
    mvaddstr(y, PTYPE_START,
	     (p_info[i].id == UNUSABLE ? "" :
	      (IS_LOGICAL(p_info[i].num) ? "Logical" :
	       (p_info[i].num >= 0 ? "Primary" :
		(p_info[i].num == PRI_OR_LOG ? "Pri/Log" :
		 (p_info[i].num == PRIMARY ? "Primary" : "Logical"))))));
    if (p_info[i].id == UNUSABLE)
	mvaddstr(y, FSTYPE_START, "Unusable");
    else if (p_info[i].id == FREE_SPACE)
	mvaddstr(y, FSTYPE_START, "Free Space");
    else if (partition_type[p_info[i].id])
	mvaddstr(y, FSTYPE_START, partition_type[p_info[i].id]);
    else
	mvprintw(y, FSTYPE_START, "Unknown (%02X)", p_info[i].id);

    if (p_info[i].dos_label[0]) {
	int l = strlen(p_info[i].dos_label);
	mvprintw(y, SIZE_START-5-l, " [%s]  ", p_info[i].dos_label);
    }

    size = p_info[i].last_sector - p_info[i].first_sector + 1;
    if (display_units == SECTORS)
	mvprintw(y, SIZE_START, "%9d", size);
    else if (display_units == CYLINDERS)
	mvprintw(y, SIZE_START, "%9d", size/(sectors*heads));
    else
	mvprintw(y, SIZE_START, "%9.2f", ceiling(size/20.48)/100);
    if (((size/(sectors*heads)) != ceiling(size/(sectors*(float)heads))) ||
	((p_info[i].first_sector/(sectors*heads)) !=
	 ceiling(p_info[i].first_sector/(sectors*heads))))
	mvprintw(y, COLUMNS-1, "*");
}

void init_const(void)
{
    if (!defined) {
	NAME_START = (((float)NAME_START)/COLUMNS)*COLS;
	FLAGS_START = (((float)FLAGS_START)/COLUMNS)*COLS;
	PTYPE_START = (((float)PTYPE_START)/COLUMNS)*COLS;
	FSTYPE_START = (((float)FSTYPE_START)/COLUMNS)*COLS;
	SIZE_START = (((float)SIZE_START)/COLUMNS)*COLS;
	COMMAND_LINE_X = (((float)COMMAND_LINE_X)/COLUMNS)*COLS;

	COMMAND_LINE_Y = LINES - 4;
	WARNING_START = LINES - 2;

	if ((NUM_ON_SCREEN = COMMAND_LINE_Y - DISK_TABLE_START - 3) <= 0)
	    NUM_ON_SCREEN = 1;

	COLUMNS = COLS;
	defined = TRUE;
    }
}

void draw_screen(void)
{
    int i;
    char *line;

    line = (char *)malloc((COLS+1)*sizeof(char));

    if (warning_last_time) {
	for (i = 0; i < COLS; i++) {
	    move(WARNING_START, i);
	    line[i] = inch();
	}
	line[COLS] = 0;
    }

    erase();

    if (warning_last_time)
	mvaddstr(WARNING_START, 0, line);


    sprintf(line, "cfdisk %s", VERSION);
    mvaddstr(HEADER_START, (COLS-strlen(line))/2, line);
    sprintf(line, "Disk Drive: %s", disk_device);
    mvaddstr(HEADER_START+2, (COLS-strlen(line))/2, line);
    sprintf(line, "Heads: %d   Sectors per Track: %d   Cylinders: %d",
	    heads, sectors, cylinders);
    mvaddstr(HEADER_START+3, (COLS-strlen(line))/2, line);

    mvaddstr(DISK_TABLE_START, NAME_START, "Name");
    mvaddstr(DISK_TABLE_START, FLAGS_START, "Flags");
    mvaddstr(DISK_TABLE_START, PTYPE_START, "Part Type");
    mvaddstr(DISK_TABLE_START, FSTYPE_START, "FS Type");
    if (display_units == SECTORS)
	mvaddstr(DISK_TABLE_START, SIZE_START, "  Sectors");
    else if (display_units == CYLINDERS)
	mvaddstr(DISK_TABLE_START, SIZE_START, "Cylinders");
    else
	mvaddstr(DISK_TABLE_START, SIZE_START, "Size (MB)");

    move(DISK_TABLE_START+1, 1);
    for (i = 1; i < COLS-1; i++)
	addch('-');

    if (NUM_ON_SCREEN >= num_parts)
	for (i = 0; i < num_parts; i++)
	    draw_partition(i);
    else
	for (i = (cur_part/NUM_ON_SCREEN)*NUM_ON_SCREEN;
	     i < NUM_ON_SCREEN + (cur_part/NUM_ON_SCREEN)*NUM_ON_SCREEN &&
	     i < num_parts;
	     i++)
	    draw_partition(i);

    free(line);
}

int draw_cursor(int move)
{
    if (move != 0 && (cur_part + move < 0 || cur_part + move >= num_parts))
	return -1;

    if (arrow_cursor)
	mvaddstr(DISK_TABLE_START + cur_part + 2
		 - (cur_part/NUM_ON_SCREEN)*NUM_ON_SCREEN, 0, "   ");
    else
	draw_partition(cur_part);

    cur_part += move;

    if (((cur_part - move)/NUM_ON_SCREEN)*NUM_ON_SCREEN !=
	(cur_part/NUM_ON_SCREEN)*NUM_ON_SCREEN)
	draw_screen();

    if (arrow_cursor)
	mvaddstr(DISK_TABLE_START + cur_part + 2
		 - (cur_part/NUM_ON_SCREEN)*NUM_ON_SCREEN, 0, "-->");
    else {
	standout();
	draw_partition(cur_part);
	standend();
    }

    return 0;
}

void do_curses_fdisk(void)
{
    int done = FALSE;
    char command;

    static struct MenuItem menuMain[]=
    {
        { 'b', "Bootable", "Toggle bootable flag of the current partition" },
        { 'd', "Delete", "Delete the current partition" },
        { 'g', "Geometry", "Change disk geometry (experts only)" },
        { 'h', "Help", "Print help screen" },
        { 'm', "Maximize", "Maximize disk usage of the current partition (experts only)" },
        { 'n', "New", "Create new partition from free space" },
        { 'p', "Print", "Print partition table to the screen or to a file" },
        { 'q', "Quit", "Quit program without writing partition table" },
        { 't', "Type", "Change the filesystem type (DOS, Linux, OS/2 and so on)" },
        { 'u', "Units", "Change units of the partition size display (MB, sect, cyl)" },
        { 'W', "Write", "Write partition table to disk (this might destroy data)" },
        { 0, NULL, NULL }
    };
    curses_started = 1;
    initscr();
    old_SIGINT = signal(SIGINT, die);
    old_SIGTERM = signal(SIGTERM, die);
#ifdef DEBUG
    signal(SIGINT, old_SIGINT);
    signal(SIGTERM, old_SIGTERM);
#endif

    cbreak();
    noecho();
    nonl();

    init_const();

    fill_p_info();

    draw_screen();

    while (!done) {
	char *s;

	(void)draw_cursor(0);

	if (p_info[cur_part].id == FREE_SPACE) {
	    s = ((opentype == O_RDWR) ? "hnpquW" : "hnpqu");
	    command = menuSelect(COMMAND_LINE_Y, COMMAND_LINE_X, menuMain, 8,
	        s, MENU_HORIZ | MENU_BUTTON | MENU_ACCEPT_OTHERS, 0);
	} else if (p_info[cur_part].id > 0) {
	    s = ((opentype == O_RDWR) ? "bdhmpqtuW" : "bdhmpqtu");
	    command = menuSelect(COMMAND_LINE_Y, COMMAND_LINE_X, menuMain, 8,
	        s, MENU_HORIZ | MENU_BUTTON | MENU_ACCEPT_OTHERS, 0);
	} else {
	    s = ((opentype == O_RDWR) ? "hpquW" : "hpqu");
	    command = menuSelect(COMMAND_LINE_Y, COMMAND_LINE_X, menuMain, 8,
	        s, MENU_HORIZ | MENU_BUTTON | MENU_ACCEPT_OTHERS, 0);
	}
	switch ( command ) {
	case 'B':
	case 'b':
	    if (p_info[cur_part].id > 0)
		p_info[cur_part].flags ^= 0x80;
	    else
		print_warning(NO_FLAGS);
	    break;
	case 'D':
	case 'd':
	    if (p_info[cur_part].id > 0) {
		del_part(cur_part);
		if (cur_part >= num_parts)
		    cur_part = num_parts - 1;
		draw_screen();
	    } else
		print_warning(DEL_EMPTY);
	    break;
	case 'G':
	case 'g':
	    if (change_geometry())
		draw_screen();
	    break;
	case 'M':
	case 'm':
	    if (p_info[cur_part].id > 0) {
		if (p_info[cur_part].first_sector == 0 ||
		    IS_LOGICAL(p_info[cur_part].num)) {
		    if (p_info[cur_part].offset == sectors)
			p_info[cur_part].offset = 1;
		    else
			p_info[cur_part].offset = sectors;
		    draw_screen();
		} else if (p_info[cur_part].offset != 0)
		    p_info[cur_part].offset = 0;
		else
		    print_warning(MAX_UNMAXABLE);
	    } else
		print_warning(MAX_UNMAXABLE);
	    break;
	case 'N':
	case 'n':
	    if (p_info[cur_part].id == FREE_SPACE) {
		new_part(cur_part);
		draw_screen();
	    } else if (p_info[cur_part].id == UNUSABLE)
		print_warning(ADD_UNUSABLE);
	    else
		print_warning(ADD_EXISTS);
	    break;
	case 'P':
	case 'p':
	    print_tables();
	    draw_screen();
	    break;
	case 'Q':
	case 'q':
	    done = TRUE;
	    break;
	case 'T':
	case 't':
	    if (p_info[cur_part].id > 0) {
		change_id(cur_part);
		draw_screen();
	    } else
		print_warning(TYPE_EMPTY);
	    break;
	case 'U':
	case 'u':
	    if (display_units == MEGABYTES)
		display_units = SECTORS;
	    else if (display_units == SECTORS)
		display_units = CYLINDERS;
	    else if (display_units == CYLINDERS)
		display_units = MEGABYTES;
	    draw_screen();
	    break;
	case 'W':
	    write_part_table();
	    break;
	case 'H':
	case 'h':
	case '?':
	    display_help();
	    draw_screen();
	    break;
	case MENU_UP : /* Up arrow */
	    if (!draw_cursor(-1))
		command = 0;
	    else
		print_warning(NO_MORE_PARTS);
	    break;
	case MENU_DOWN : /* Down arrow */
	    if (!draw_cursor(1))
		command = 0;
	    else
		print_warning(NO_MORE_PARTS);
	    break;
	case REDRAWKEY:
	    clear();
	    draw_screen();
	    break;
	default:
	    print_warning(BAD_COMMAND);
	    putchar(BELL); /* CTRL-G */
	}
    }

    die_x(0);
}

void copyright(void)
{
    fprintf(stderr, "Copyright (C) 1994-1997 Kevin E. Martin & aeb\n");
}

void usage(char *prog_name)
{
    fprintf(stderr, "\nUsage:\n");
    fprintf(stderr, "Print version:\n");
    fprintf(stderr, "\t%s -v\n", prog_name);
    fprintf(stderr, "Print partition table:\n");
    fprintf(stderr, "\t%s -P {r|s|t} [options] device\n", prog_name);
    fprintf(stderr, "Interactive use:\n");
    fprintf(stderr, "\t%s [options] device\n", prog_name);
    fprintf(stderr, "
Options:
-a: Use arrow instead of highlighting;
-z: Start with a zero partition table, instead of reading the pt from disk;
-c C -h H -s S: Override the kernel's idea of the number of cylinders,
                the number of heads and the number of sectors/track.\n\n");

    copyright();
}

int main(int argc, char **argv)
{
    char c;
    int i, len;

    while ((c = getopt(argc, argv, "ac:h:s:vzP:")) != EOF)
	switch (c) {
	case 'a':
	    arrow_cursor = TRUE;
	    break;
	case 'c':
	    cylinders = atoi(optarg);
	    if (cylinders <= 0 || cylinders > MAX_CYLINDERS) {
		fprintf(stderr, "%s: %s\n", argv[0], BAD_CYLINDERS);
		exit(1);
	    }
	    break;
	case 'h':
	    heads = atoi(optarg);
	    if (heads <= 0 || heads > MAX_HEADS) {
		fprintf(stderr, "%s: %s\n", argv[0], BAD_HEADS);
		exit(1);
	    }
	    break;
	case 's':
	    sectors = atoi(optarg);
	    if (sectors <= 0 || sectors > MAX_SECTORS) {
		fprintf(stderr, "%s: %s\n", argv[0], BAD_SECTORS);
		exit(1);
	    }
	    break;
	case 'v':
	    fprintf(stderr, "cfdisk %s\n", VERSION);
	    copyright();
	    exit(0);
	case 'z':
	    zero_table = TRUE;
	    break;
	case 'P':
	    len = strlen(optarg);
	    for (i = 0; i < len; i++) {
		switch (optarg[i]) {
		case 'r':
		    print_only |= PRINT_RAW_TABLE;
		    break;
		case 's':
		    print_only |= PRINT_SECTOR_TABLE;
		    break;
		case 't':
		    print_only |= PRINT_PARTITION_TABLE;
		    break;
		default:
		    usage(argv[0]);
		    break;
		}
	    }
	    break;
	default:
	    usage(argv[0]);
	    exit(1);
	}

    if (argc-optind == 1)
	disk_device = argv[optind];
    else if (argc-optind != 0) {
	usage(argv[0]);
	exit(1);
    } else if ((fd = open(DEFAULT_DEVICE, O_RDONLY)) < 0)
	disk_device = ALTERNATE_DEVICE;
    else close(fd);

    if (print_only) {
	fill_p_info();
	if (print_only & PRINT_RAW_TABLE)
	    print_raw_table();
	if (print_only & PRINT_SECTOR_TABLE)
	    print_p_info();
	if (print_only & PRINT_PARTITION_TABLE)
	    print_part_table();
    } else
	do_curses_fdisk();
    return 0;
}
