/* 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.
 */
 
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
 
#include <linux/genhd.h>
#include <linux/hdreg.h>
 
#define hex_val(c)      ({ \
                                char _c = (c); \
                                isdigit(_c) ? _c - '0' : \
                                        tolower(_c) + 10 - 'a'; \
                        })
#define VERSION         "0.93"
#define DEFAULT_DEVICE  "/dev/hda"
#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))
#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)
#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
 
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 */
        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 user"},
                {4, "DOS 16-bit <32M"},
                {EXTENDED, "Extended"},
                {6, "DOS 16-bit >=32M"},
                {7, "OS/2 HPFS"},
                {8, "AIX"},
                {9, "AIX bootable"},
                {10, "OPUS"},
                {0x40, "Venix"},
                {0x51, "Novell?"},
                {0x52, "Microport"},
                {0x63, "GNU HURD"},
                {0x64, "Novell"},
                {0x75, "PC/IX"},
                {0x80, "Old MINIX"},
                {LINUX_PARTITION, "Linux/MINIX"},
                {LINUX_SWAP, "Linux swap"},
                {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 fatal(enum failure why)
{
        char    error[LINE_LENGTH],
                *message = error;
 
        switch (why) {
                case usage: message = "Usage: fdisk [-l] [-v] [/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;
        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);
        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;
        printf("%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)
                printf("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)
                printf("Bad offset in primary extended partition\n");
        else while (p->sys_ind == EXTENDED) {
                if (partitions >= MAXIMUM_PARTS) {
                        printf("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])
                                        printf("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])
                                        printf("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) {
                                printf("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) {
                        printf("Warning: invalid flag %04x of partition 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) {
                printf("Partition %d is an extended partition\n",
                        i + 1);
                p->boot_ind = 0;
        }
        else 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;
                        }
                }
        }
}
 
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");
}
 
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);
        }
}
 
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)) {
                        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;
        }
 
        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;
                        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: Linux 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);
        }
        close(fd);
        sync();
        printf("The partition table has been altered.\n"
                "Please reboot before doing anything else.\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 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':
				disk_device = "/dev/hda";
				get_boot();
				list_table();
				disk_device = "/dev/hdb";
				get_boot();
				list_table();
				exit(0);
			default:
				fatal(usage);
		}
        }
        if (argc > 1)
                disk_device = argv[argc - 1];
        get_boot();
 
        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();
                }
        }
}
