diff options
author | Karel Zak | 2013-06-26 10:21:31 +0200 |
---|---|---|
committer | Karel Zak | 2013-09-16 16:47:04 +0200 |
commit | 5099226711aa6e4ea254c24b4d496d32a51d3bb1 (patch) | |
tree | a08899a66c0300f3f5a3b772dd1cc96130aac40e /libfdisk/src/dos.c | |
parent | fdisk: (dos) use libfdisk to ask for partition type (diff) | |
download | kernel-qcow2-util-linux-5099226711aa6e4ea254c24b4d496d32a51d3bb1.tar.gz kernel-qcow2-util-linux-5099226711aa6e4ea254c24b4d496d32a51d3bb1.tar.xz kernel-qcow2-util-linux-5099226711aa6e4ea254c24b4d496d32a51d3bb1.zip |
libfdisk: move mbr code to the library
Signed-off-by: Karel Zak <kzak@redhat.com>
Diffstat (limited to 'libfdisk/src/dos.c')
-rw-r--r-- | libfdisk/src/dos.c | 1916 |
1 files changed, 1916 insertions, 0 deletions
diff --git a/libfdisk/src/dos.c b/libfdisk/src/dos.c new file mode 100644 index 000000000..ef426cfa1 --- /dev/null +++ b/libfdisk/src/dos.c @@ -0,0 +1,1916 @@ +/* + * + * Copyright (C) 2007-2013 Karel Zak <kzak@redhat.com> + * 2012 Davidlohr Bueso <dave@gnu.org> + * + * This is re-written version for libfdisk, the original was fdiskdoslabel.c + * from util-linux fdisk. + */ +#include "c.h" +#include "nls.h" +#include "randutils.h" +#include "pt-mbr.h" + +#include "fdiskP.h" + +#include <ctype.h> + +#define MAXIMUM_PARTS 60 +#define ACTIVE_FLAG 0x80 + +#define IS_EXTENDED(i) \ + ((i) == MBR_DOS_EXTENDED_PARTITION \ + || (i) == MBR_W95_EXTENDED_PARTITION \ + || (i) == MBR_LINUX_EXTENDED_PARTITION) + +/* + * per partition table entry data + * + * The four primary partitions have the same sectorbuffer + * and have NULL ex_entry. + * + * Each logical partition table entry has two pointers, one for the + * partition and one link to the next one. + */ +struct pte { + struct dos_partition *pt_entry; /* on-disk MBR entry */ + struct dos_partition *ex_entry; /* on-disk EBR entry */ + sector_t offset; /* disk sector number */ + unsigned char *sectorbuffer; /* disk sector contents */ + + unsigned int changed : 1, + private_sectorbuffer : 1; +}; + +/* + * in-memory fdisk GPT stuff + */ +struct fdisk_dos_label { + struct fdisk_label head; /* generic part */ + + struct pte ptes[MAXIMUM_PARTS]; /* partition */ + sector_t ext_offset; + size_t ext_index; + unsigned int compatible : 1, /* is DOS compatible? */ + non_pt_changed : 1; /* MBR, but no PT changed */ +}; + +/* + * Partition types + */ +static struct fdisk_parttype dos_parttypes[] = { + #include "pt-mbr-partnames.h" +}; + +#define set_hsc(h,s,c,sector) { \ + s = sector % cxt->geom.sectors + 1; \ + sector /= cxt->geom.sectors; \ + h = sector % cxt->geom.heads; \ + sector /= cxt->geom.heads; \ + c = sector & 0xff; \ + s |= (sector >> 2) & 0xc0; \ + } + + +#define sector(s) ((s) & 0x3f) +#define cylinder(s, c) ((c) | (((s) & 0xc0) << 2)) + +#define alignment_required(_x) ((_x)->grain != (_x)->sector_size) + +#define is_dos_compatible(_x) \ + (fdisk_is_disklabel(_x, DOS) && \ + fdisk_dos_is_compatible(fdisk_context_get_label(_x, NULL))) + +#define cround(c, n) fdisk_scround(c, n) + + +static inline struct fdisk_dos_label *self_label(struct fdisk_context *cxt) +{ + assert(cxt); + assert(cxt->label); + assert(fdisk_is_disklabel(cxt, DOS)); + + return (struct fdisk_dos_label *) cxt->label; +} + +static inline struct pte *self_pte(struct fdisk_context *cxt, size_t i) +{ + struct fdisk_dos_label *l = self_label(cxt); + + if (i >= ARRAY_SIZE(l->ptes)) + return NULL; + + return &l->ptes[i]; +} + +static inline struct dos_partition *self_partition( + struct fdisk_context *cxt, + size_t i) +{ + struct pte *pe = self_pte(cxt, i); + return pe ? pe->pt_entry : NULL; +} + +struct dos_partition *fdisk_dos_get_partition( + struct fdisk_context *cxt, + size_t i) +{ + return self_partition(cxt, i); +} + +static void partition_set_changed( + struct fdisk_context *cxt, + size_t i, + int changed) +{ + struct pte *pe = self_pte(cxt, i); + + if (!pe) + return; + + pe->changed = changed ? 1 : 0; + if (changed) + fdisk_label_set_changed(cxt->label, 1); +} + +static sector_t get_abs_partition_start(struct pte *pe) +{ + return pe->offset + dos_partition_get_start(pe->pt_entry); +} + +static int is_cleared_partition(struct dos_partition *p) +{ + return !(!p || p->boot_ind || p->bh || p->bs || p->bc || + p->sys_ind || p->eh || p->es || p->ec || + dos_partition_get_start(p) || dos_partition_get_size(p)); +} + +static int get_partition_unused_primary(struct fdisk_context *cxt) +{ + size_t orgmax = cxt->label->nparts_max; + size_t n; + int rc; + + cxt->label->nparts_max = 4; + rc = fdisk_ask_partnum(cxt, &n, TRUE); + cxt->label->nparts_max = orgmax; + + switch (rc) { + case 1: + fdisk_info(cxt, _("All primary partitions have been defined already")); + return -1; + case 0: + return n; + default: + return rc; + } +} + +static int seek_sector(struct fdisk_context *cxt, sector_t secno) +{ + off_t offset = (off_t) secno * cxt->sector_size; + + return lseek(cxt->dev_fd, offset, SEEK_SET) == (off_t) -1 ? -errno : 0; +} + +static int read_sector(struct fdisk_context *cxt, sector_t secno, + unsigned char *buf) +{ + int rc = seek_sector(cxt, secno); + + if (rc < 0) + return rc; + + return read(cxt->dev_fd, buf, cxt->sector_size) != + (ssize_t) cxt->sector_size ? -errno : 0; +} + +/* Allocate a buffer and read a partition table sector */ +static int read_pte(struct fdisk_context *cxt, int pno, sector_t offset) +{ + unsigned char *buf; + struct pte *pe = self_pte(cxt, pno); + + buf = calloc(1, cxt->sector_size); + if (!buf) + return -ENOMEM; + + pe->offset = offset; + pe->sectorbuffer = buf; + pe->private_sectorbuffer = 1; + + if (read_sector(cxt, offset, pe->sectorbuffer) != 0) + fdisk_warn(cxt, _("Failed to read extended partition table " + "(offset=%jd)"), (uintmax_t) offset); + pe->changed = 0; + pe->pt_entry = pe->ex_entry = NULL; + return 0; +} + + +static void clear_partition(struct dos_partition *p) +{ + if (!p) + return; + p->boot_ind = 0; + p->bh = 0; + p->bs = 0; + p->bc = 0; + p->sys_ind = 0; + p->eh = 0; + p->es = 0; + p->ec = 0; + dos_partition_set_start(p,0); + dos_partition_set_size(p,0); +} + +static void dos_init(struct fdisk_context *cxt) +{ + struct fdisk_dos_label *l = self_label(cxt); + size_t i; + + cxt->label->nparts_max = 4; /* default, unlimited number of logical */ + + l->ext_index = 0; + l->ext_offset = 0; + l->non_pt_changed = 0; + + memset(l->ptes, 0, sizeof(l->ptes)); + + for (i = 0; i < 4; i++) { + struct pte *pe = self_pte(cxt, i); + + pe->pt_entry = mbr_get_partition(cxt->firstsector, i); + pe->ex_entry = NULL; + pe->offset = 0; + pe->sectorbuffer = cxt->firstsector; + pe->changed = 0; + } + + if (fdisk_context_listonly(cxt)) + return; + /* + * Various warnings... + */ + if (fdisk_missing_geometry(cxt)) + fdisk_warnx(cxt, _("You can set geometry from the extra functions menu.")); + + if (is_dos_compatible(cxt)) { + fdisk_warnx(cxt, _("DOS-compatible mode is deprecated.")); + + if (cxt->sector_size != cxt->phy_sector_size) + fdisk_info(cxt, _( + "The device presents a logical sector size that is smaller than " + "the physical sector size. Aligning to a physical sector (or optimal " + "I/O) size boundary is recommended, or performance may be impacted.")); + } + + if (fdisk_context_use_cylinders(cxt)) + fdisk_warnx(cxt, _("Cylinders as display units are deprecated.")); + + if (cxt->total_sectors > UINT_MAX) { + unsigned long long bytes = cxt->total_sectors * cxt->sector_size; + int giga = bytes / 1000000000; + int hectogiga = (giga + 50) / 100; + + fdisk_warnx(cxt, + _("The size of this disk is %d.%d TB (%llu bytes). DOS " + "partition table format can not be used on drives for " + "volumes larger than (%llu bytes) for %ld-byte " + "sectors. Use GUID partition table format (GPT)."), + hectogiga / 10, hectogiga % 10, + bytes, + (sector_t ) UINT_MAX * cxt->sector_size, + cxt->sector_size); + } +} + +/* callback called by libfdisk */ +static void dos_deinit(struct fdisk_label *lb) +{ + size_t i; + struct fdisk_dos_label *l = (struct fdisk_dos_label *) lb; + + for (i = 0; i < ARRAY_SIZE(l->ptes); i++) { + struct pte *pe = &l->ptes[i]; + + if (pe->private_sectorbuffer) + free(pe->sectorbuffer); + pe->sectorbuffer = NULL; + } + + memset(l->ptes, 0, sizeof(l->ptes)); +} + +static int dos_delete_partition(struct fdisk_context *cxt, size_t partnum) +{ + struct fdisk_dos_label *l = self_label(cxt); + struct pte *pe = self_pte(cxt, partnum); + struct dos_partition *p; + struct dos_partition *q; + + if (!pe) + return -EINVAL; + + p = pe->pt_entry; + q = pe->ex_entry; + + /* Note that for the fifth partition (partnum == 4) we don't actually + decrement partitions. */ + if (partnum < 4) { + if (IS_EXTENDED(p->sys_ind) && partnum == l->ext_index) { + cxt->label->nparts_max = 4; + l->ptes[l->ext_index].ex_entry = NULL; + l->ext_offset = 0; + } + partition_set_changed(cxt, partnum, 1); + clear_partition(p); + } else if (!q->sys_ind && partnum > 4) { + /* the last one in the chain - just delete */ + --cxt->label->nparts_max; + --partnum; + clear_partition(l->ptes[partnum].ex_entry); + partition_set_changed(cxt, partnum, 1); + } else { + /* not the last one - further ones will be moved down */ + if (partnum > 4) { + /* delete this link in the chain */ + p = l->ptes[partnum - 1].ex_entry; + *p = *q; + dos_partition_set_start(p, dos_partition_get_start(q)); + dos_partition_set_size(p, dos_partition_get_size(q)); + partition_set_changed(cxt, partnum - 1, 1); + } else if (cxt->label->nparts_max > 5) { /* 5 will be moved to 4 */ + /* the first logical in a longer chain */ + pe = &l->ptes[5]; + + if (pe->pt_entry) /* prevent SEGFAULT */ + dos_partition_set_start(pe->pt_entry, + get_abs_partition_start(pe) - + l->ext_offset); + pe->offset = l->ext_offset; + partition_set_changed(cxt, 5, 1); + } + + if (cxt->label->nparts_max > 5) { + cxt->label->nparts_max--; + while (partnum < cxt->label->nparts_max) { + l->ptes[partnum] = l->ptes[partnum + 1]; + partnum++; + } + } else + /* the only logical: clear only */ + clear_partition(l->ptes[partnum].pt_entry); + } + + fdisk_label_set_changed(cxt->label, 1); + return 0; +} + +static void read_extended(struct fdisk_context *cxt, int ext) +{ + size_t i; + struct pte *pex; + struct dos_partition *p, *q; + struct fdisk_dos_label *l = self_label(cxt); + + l->ext_index = ext; + pex = self_pte(cxt, ext); + pex->ex_entry = pex->pt_entry; + + p = pex->pt_entry; + if (!dos_partition_get_start(p)) { + fdisk_warnx(cxt, _("Bad offset in primary extended partition")); + return; + } + + while (IS_EXTENDED (p->sys_ind)) { + struct pte *pe = self_pte(cxt, cxt->label->nparts_max); + + if (cxt->label->nparts_max >= MAXIMUM_PARTS) { + /* This is not a Linux restriction, but + this program uses arrays of size MAXIMUM_PARTS. + Do not try to `improve' this test. */ + struct pte *pre = self_pte(cxt, + cxt->label->nparts_max - 1); + fdisk_warnx(cxt, + _("Omitting partitions after #%zd. They will be deleted " + "if you save this partition table."), + cxt->label->nparts_max); + + clear_partition(pre->ex_entry); + partition_set_changed(cxt, + cxt->label->nparts_max - 1, 1); + return; + } + + read_pte(cxt, cxt->label->nparts_max, + l->ext_offset + dos_partition_get_start(p)); + + if (!l->ext_offset) + l->ext_offset = dos_partition_get_start(p); + + q = p = mbr_get_partition(pe->sectorbuffer, 0); + + for (i = 0; i < 4; i++, p++) if (dos_partition_get_size(p)) { + if (IS_EXTENDED (p->sys_ind)) { + if (pe->ex_entry) + fdisk_warnx(cxt, _( + "Extra link pointer in partition " + "table %zd"), + cxt->label->nparts_max + 1); + else + pe->ex_entry = p; + } else if (p->sys_ind) { + if (pe->pt_entry) + fdisk_warnx(cxt, _( + "Ignoring extra data in partition " + "table %zd"), + cxt->label->nparts_max + 1); + else + pe->pt_entry = p; + } + } + + /* very strange code here... */ + if (!pe->pt_entry) { + if (q != pe->ex_entry) + pe->pt_entry = q; + else + pe->pt_entry = q + 1; + } + if (!pe->ex_entry) { + if (q != pe->pt_entry) + pe->ex_entry = q; + else + pe->ex_entry = q + 1; + } + + p = pe->ex_entry; + cxt->label->nparts_cur = ++cxt->label->nparts_max; + } + + /* remove empty links */ + remove: + q = self_partition(cxt, 4); + for (i = 4; i < cxt->label->nparts_max; i++) { + p = self_partition(cxt, i); + + if (!dos_partition_get_size(p) && + (cxt->label->nparts_max > 5 || q->sys_ind)) { + fdisk_info(cxt, _("omitting empty partition (%zd)"), i+1); + dos_delete_partition(cxt, i); + goto remove; /* numbering changed */ + } + } +} + +static int dos_get_disklabel_id(struct fdisk_context *cxt, char **id) +{ + unsigned int num; + + assert(cxt); + assert(id); + assert(cxt->label); + assert(fdisk_is_disklabel(cxt, DOS)); + + num = mbr_get_id(cxt->firstsector); + if (asprintf(id, "0x%08x", num) > 0) + return 0; + + return -ENOMEM; +} + +static int dos_create_disklabel(struct fdisk_context *cxt) +{ + unsigned int id; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_disklabel(cxt, DOS)); + + /* random disk signature */ + random_get_bytes(&id, sizeof(id)); + + fdisk_info(cxt, ("Building a new DOS disklabel with disk " + "identifier 0x%08x."), id); + + dos_init(cxt); + fdisk_zeroize_firstsector(cxt); + fdisk_label_set_changed(cxt->label, 1); + + /* Generate an MBR ID for this disk */ + mbr_set_id(cxt->firstsector, id); + + /* Put MBR signature */ + mbr_set_magic(cxt->firstsector); + return 0; +} + +static int dos_set_disklabel_id(struct fdisk_context *cxt) +{ + char *end = NULL, *str = NULL; + unsigned int id, old; + struct fdisk_dos_label *l; + int rc; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_disklabel(cxt, DOS)); + + l = self_label(cxt); + old = mbr_get_id(cxt->firstsector); + rc = fdisk_ask_string(cxt, + _("Enter of the new disk identifier"), &str); + if (rc) + return rc; + + errno = 0; + id = strtoul(str, &end, 0); + if (errno || str == end || (end && *end)) { + fdisk_warnx(cxt, _("Incorrect value.")); + return -EINVAL; + } + + fdisk_info(cxt, _("Changing disk identifier from 0x%08x to 0x%08x."), + old, id); + + mbr_set_id(cxt->firstsector, id); + l->non_pt_changed = 1; + fdisk_label_set_changed(cxt->label, 1); + return 0; +} + +static void get_partition_table_geometry(struct fdisk_context *cxt, + unsigned int *ph, unsigned int *ps) +{ + unsigned char *bufp = cxt->firstsector; + struct dos_partition *p; + int i, h, s, hh, ss; + int first = 1; + int bad = 0; + + hh = ss = 0; + for (i=0; i<4; i++) { + p = mbr_get_partition(bufp, i); + if (p->sys_ind != 0) { + h = p->eh + 1; + s = (p->es & 077); + if (first) { + hh = h; + ss = s; + first = 0; + } else if (hh != h || ss != s) + bad = 1; + } + } + + if (!first && !bad) { + *ph = hh; + *ps = ss; + } + + DBG(LABEL, dbgprint("DOS PT geometry: heads=%u, sectors=%u", *ph, *ps)); +} + +static int dos_reset_alignment(struct fdisk_context *cxt) +{ + assert(cxt); + assert(cxt->label); + assert(fdisk_is_disklabel(cxt, DOS)); + + /* overwrite necessary stuff by DOS deprecated stuff */ + if (is_dos_compatible(cxt)) { + if (cxt->geom.sectors) + cxt->first_lba = cxt->geom.sectors; /* usually 63 */ + + cxt->grain = cxt->sector_size; /* usually 512 */ + } + + return 0; +} + +/* TODO: move to include/pt-dos.h and share with libblkid */ +#define AIX_MAGIC_STRING "\xC9\xC2\xD4\xC1" +#define AIX_MAGIC_STRLEN (sizeof(AIX_MAGIC_STRING) - 1) + +static int dos_probe_label(struct fdisk_context *cxt) +{ + size_t i; + unsigned int h = 0, s = 0; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_disklabel(cxt, DOS)); + + /* ignore disks with AIX magic number */ + if (memcmp(cxt->firstsector, AIX_MAGIC_STRING, AIX_MAGIC_STRLEN) == 0) + return 0; + + if (!mbr_is_valid_magic(cxt->firstsector)) + return 0; + + dos_init(cxt); + + get_partition_table_geometry(cxt, &h, &s); + if (h && s) { + cxt->geom.heads = h; + cxt->geom.sectors = s; + } + + for (i = 0; i < 4; i++) { + struct pte *pe = self_pte(cxt, i); + + if (!is_cleared_partition(pe->pt_entry)) + cxt->label->nparts_cur++; + + if (IS_EXTENDED (pe->pt_entry->sys_ind)) { + if (cxt->label->nparts_max != 4) + fdisk_warnx(cxt, _( + "Ignoring extra extended partition %zd"), + i + 1); + else + read_extended(cxt, i); + } + } + + for (i = 3; i < cxt->label->nparts_max; i++) { + struct pte *pe = self_pte(cxt, i); + + if (!mbr_is_valid_magic(pe->sectorbuffer)) { + fdisk_info(cxt, _( + "Invalid flag 0x%02x%02x of partition table %zd will " + "be corrected by w(rite)"), + pe->sectorbuffer[510], + pe->sectorbuffer[511], + i + 1); + partition_set_changed(cxt, 1, 1); + } + } + + return 1; +} + +/* + * Avoid warning about DOS partitions when no DOS partition was changed. + * Here a heuristic "is probably dos partition". + * We might also do the opposite and warn in all cases except + * for "is probably nondos partition". + */ +static int is_dos_partition(int t) +{ + return (t == 1 || t == 4 || t == 6 || + t == 0x0b || t == 0x0c || t == 0x0e || + t == 0x11 || t == 0x12 || t == 0x14 || t == 0x16 || + t == 0x1b || t == 0x1c || t == 0x1e || t == 0x24 || + t == 0xc1 || t == 0xc4 || t == 0xc6); +} + +static void set_partition(struct fdisk_context *cxt, + int i, int doext, sector_t start, + sector_t stop, int sysid) +{ + struct pte *pe = self_pte(cxt, i); + struct dos_partition *p; + sector_t offset; + + if (doext) { + struct fdisk_dos_label *l = self_label(cxt); + p = pe->ex_entry; + offset = l->ext_offset; + } else { + p = pe->pt_entry; + offset = pe->offset; + } + p->boot_ind = 0; + p->sys_ind = sysid; + dos_partition_set_start(p, start - offset); + dos_partition_set_size(p, stop - start + 1); + + if (!doext) { + struct fdisk_parttype *t = fdisk_get_parttype_from_code(cxt, sysid); + fdisk_info_new_partition(cxt, i + 1, start, stop, t); + } + if (is_dos_compatible(cxt) && (start/(cxt->geom.sectors*cxt->geom.heads) > 1023)) + start = cxt->geom.heads*cxt->geom.sectors*1024 - 1; + set_hsc(p->bh, p->bs, p->bc, start); + if (is_dos_compatible(cxt) && (stop/(cxt->geom.sectors*cxt->geom.heads) > 1023)) + stop = cxt->geom.heads*cxt->geom.sectors*1024 - 1; + set_hsc(p->eh, p->es, p->ec, stop); + partition_set_changed(cxt, i, 1); +} + +static sector_t get_unused_start(struct fdisk_context *cxt, + int part_n, sector_t start, + sector_t first[], sector_t last[]) +{ + size_t i; + + for (i = 0; i < cxt->label->nparts_max; i++) { + sector_t lastplusoff; + struct pte *pe = self_pte(cxt, i); + + if (start == pe->offset) + start += cxt->first_lba; + lastplusoff = last[i] + ((part_n < 4) ? 0 : cxt->first_lba); + if (start >= first[i] && start <= lastplusoff) + start = lastplusoff + 1; + } + + return start; +} + +static void fill_bounds(struct fdisk_context *cxt, + sector_t *first, sector_t *last) +{ + size_t i; + struct pte *pe = self_pte(cxt, 0); + struct dos_partition *p; + + for (i = 0; i < cxt->label->nparts_max; pe++,i++) { + p = pe->pt_entry; + if (!p->sys_ind || IS_EXTENDED (p->sys_ind)) { + first[i] = 0xffffffff; + last[i] = 0; + } else { + first[i] = get_abs_partition_start(pe); + last[i] = first[i] + dos_partition_get_size(p) - 1; + } + } +} + +static int add_partition(struct fdisk_context *cxt, int n, struct fdisk_parttype *t) +{ + int sys, read = 0, rc; + size_t i; + struct fdisk_dos_label *l = self_label(cxt); + struct dos_partition *p = self_partition(cxt, n); + struct dos_partition *q = self_partition(cxt, l->ext_index); + + sector_t start, stop = 0, limit, temp, + first[cxt->label->nparts_max], + last[cxt->label->nparts_max]; + + sys = t ? t->type : MBR_LINUX_DATA_PARTITION; + + if (p && p->sys_ind) { + fdisk_warnx(cxt, _("Partition %d is already defined. Delete " + "it before re-adding it."), n + 1); + return -EINVAL; + } + fill_bounds(cxt, first, last); + if (n < 4) { + start = cxt->first_lba; + if (fdisk_context_use_cylinders(cxt) || !cxt->total_sectors) + limit = cxt->geom.heads * cxt->geom.sectors * cxt->geom.cylinders - 1; + else + limit = cxt->total_sectors - 1; + + if (limit > UINT_MAX) + limit = UINT_MAX; + + if (l->ext_offset) { + first[l->ext_index] = l->ext_offset; + last[l->ext_index] = dos_partition_get_start(q) + + dos_partition_get_size(q) - 1; + } + } else { + start = l->ext_offset + cxt->first_lba; + limit = dos_partition_get_start(q) + + dos_partition_get_size(q) - 1; + } + if (fdisk_context_use_cylinders(cxt)) + for (i = 0; i < cxt->label->nparts_max; i++) + first[i] = (cround(cxt, first[i]) - 1) + * fdisk_context_get_units_per_sector(cxt); + + /* + * Ask for first sector + */ + do { + sector_t dflt, aligned; + + temp = start; + dflt = start = get_unused_start(cxt, n, start, first, last); + + /* the default sector should be aligned and unused */ + do { + aligned = fdisk_align_lba_in_range(cxt, dflt, dflt, limit); + dflt = get_unused_start(cxt, n, aligned, first, last); + } while (dflt != aligned && dflt > aligned && dflt < limit); + + if (dflt >= limit) + dflt = start; + if (start > limit) + break; + if (start >= temp + fdisk_context_get_units_per_sector(cxt) + && read) { + fdisk_info(cxt, _("Sector %llu is already allocated"), + temp); + temp = start; + read = 0; + } + + if (!read && start == temp) { + sector_t j = start; + struct fdisk_ask *ask = fdisk_new_ask(); + + if (fdisk_context_use_cylinders(cxt)) + fdisk_ask_set_query(ask, _("First cylinder")); + else + fdisk_ask_set_query(ask, _("First sector")); + + fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER); + fdisk_ask_number_set_low(ask, cround(cxt, j)); + fdisk_ask_number_set_default(ask, cround(cxt, dflt)); + fdisk_ask_number_set_high(ask, cround(cxt, limit)); + + rc = fdisk_do_ask(cxt, ask); + if (!rc) + start = fdisk_ask_number_get_result(ask); + fdisk_free_ask(ask); + if (rc) + return rc; + + if (fdisk_context_use_cylinders(cxt)) { + start = (start - 1) + * fdisk_context_get_units_per_sector(cxt); + if (start < j) + start = j; + } + read = 1; + } + } while (start != temp || !read); + + if (n > 4) { /* NOT for fifth partition */ + struct pte *pe = self_pte(cxt, n); + + pe->offset = start - cxt->first_lba; + if (pe->offset == l->ext_offset) { /* must be corrected */ + pe->offset++; + if (cxt->first_lba == 1) + start++; + } + } + + for (i = 0; i < cxt->label->nparts_max; i++) { + struct pte *pe = self_pte(cxt, i); + + if (start < pe->offset && limit >= pe->offset) + limit = pe->offset - 1; + if (start < first[i] && limit >= first[i]) + limit = first[i] - 1; + } + if (start > limit) { + fdisk_info(cxt, _("No free sectors available")); + if (n > 4) + cxt->label->nparts_max--; + return -ENOSPC; + } + if (cround(cxt, start) == cround(cxt, limit)) { + stop = limit; + } else { + /* + * Ask for last sector + */ + struct fdisk_ask *ask = fdisk_new_ask(); + + fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET); + + if (fdisk_context_use_cylinders(cxt)) { + fdisk_ask_set_query(ask, _("Last cylinder, +cylinders or +size{K,M,G,T,P}")); + fdisk_ask_number_set_unit(ask, + cxt->sector_size * + fdisk_context_get_units_per_sector(cxt)); + } else { + fdisk_ask_set_query(ask, _("Last sector, +sectors or +size{K,M,G,T,P}")); + fdisk_ask_number_set_unit(ask,cxt->sector_size); + } + + fdisk_ask_number_set_low(ask, cround(cxt, start)); + fdisk_ask_number_set_default(ask, cround(cxt, limit)); + fdisk_ask_number_set_high(ask, cround(cxt, limit)); + fdisk_ask_number_set_base(ask, cround(cxt, start)); /* base for relative input */ + + rc = fdisk_do_ask(cxt, ask); + if (rc) { + fdisk_free_ask(ask); + return rc; + } + + stop = fdisk_ask_number_get_result(ask); + + if (fdisk_ask_number_is_relative(ask) + && alignment_required(cxt)) { + /* the last sector has not been exactly requested (but + * defined by +size{K,M,G} convention), so be smart and + * align the end of the partition. The next partition + * will start at phy.block boundary. + */ + stop = fdisk_align_lba_in_range(cxt, stop, start, limit) - 1; + if (stop > limit) + stop = limit; + } + fdisk_free_ask(ask); + } + + set_partition(cxt, n, 0, start, stop, sys); + if (n > 4) { + struct pte *pe = self_pte(cxt, n); + set_partition(cxt, n - 1, 1, pe->offset, stop, + MBR_DOS_EXTENDED_PARTITION); + } + + if (IS_EXTENDED(sys)) { + struct pte *pe4 = self_pte(cxt, 4); + struct pte *pen = self_pte(cxt, n); + + l->ext_index = n; + pen->ex_entry = p; + pe4->offset = l->ext_offset = start; + pe4->sectorbuffer = calloc(1, cxt->sector_size); + if (!pe4->sectorbuffer) + return -ENOMEM; + pe4->private_sectorbuffer = 1; + pe4->pt_entry = mbr_get_partition(pe4->sectorbuffer, 0); + pe4->ex_entry = pe4->pt_entry + 1; + + partition_set_changed(cxt, 4, 1); + cxt->label->nparts_max = 5; + } + + fdisk_label_set_changed(cxt->label, 1); + return 0; +} + +static int add_logical(struct fdisk_context *cxt) +{ + struct dos_partition *p4 = self_partition(cxt, 4); + + assert(cxt); + assert(cxt->label); + + if (cxt->label->nparts_max > 5 || p4->sys_ind) { + struct pte *pe = self_pte(cxt, cxt->label->nparts_max); + + pe->sectorbuffer = calloc(1, cxt->sector_size); + if (!pe->sectorbuffer) + return -ENOMEM; + pe->private_sectorbuffer = 1; + pe->pt_entry = mbr_get_partition(pe->sectorbuffer, 0); + pe->ex_entry = pe->pt_entry + 1; + pe->offset = 0; + + partition_set_changed(cxt, cxt->label->nparts_max, 1); + cxt->label->nparts_max++; + } + fdisk_info(cxt, _("Adding logical partition %zd"), + cxt->label->nparts_max); + return add_partition(cxt, cxt->label->nparts_max - 1, NULL); +} + +static void check(struct fdisk_context *cxt, size_t n, + unsigned int h, unsigned int s, unsigned int c, + unsigned int start) +{ + unsigned int total, real_s, real_c; + + real_s = sector(s) - 1; + real_c = cylinder(s, c); + total = (real_c * cxt->geom.sectors + real_s) * cxt->geom.heads + h; + + if (!total) + fdisk_warnx(cxt, _("Partition %zd: contains sector 0"), n); + if (h >= cxt->geom.heads) + fdisk_warnx(cxt, _("Partition %zd: head %d greater than " + "maximum %d"), n, h + 1, cxt->geom.heads); + if (real_s >= cxt->geom.sectors) + fdisk_warnx(cxt, _("Partition %zd: sector %d greater than " + "maximum %llu"), n, s, cxt->geom.sectors); + if (real_c >= cxt->geom.cylinders) + fdisk_warnx(cxt, _("Partition %zd: cylinder %d greater than " + "maximum %llu"), + n, real_c + 1, + cxt->geom.cylinders); + + if (cxt->geom.cylinders <= 1024 && start != total) + fdisk_warnx(cxt, _("Partition %zd: previous sectors %d " + "disagrees with total %d"), n, start, total); +} + +/* check_consistency() and long2chs() added Sat Mar 6 12:28:16 1993, + * faith@cs.unc.edu, based on code fragments from pfdisk by Gordon W. Ross, + * Jan. 1990 (version 1.2.1 by Gordon W. Ross Aug. 1990; Modified by S. + * Lubkin Oct. 1991). */ + +static void +long2chs(struct fdisk_context *cxt, unsigned long ls, + unsigned int *c, unsigned int *h, unsigned int *s) { + int spc = cxt->geom.heads * cxt->geom.sectors; + + *c = ls / spc; + ls = ls % spc; + *h = ls / cxt->geom.sectors; + *s = ls % cxt->geom.sectors + 1; /* sectors count from 1 */ +} + +static void check_consistency(struct fdisk_context *cxt, struct dos_partition *p, + size_t partition) +{ + unsigned int pbc, pbh, pbs; /* physical beginning c, h, s */ + unsigned int pec, peh, pes; /* physical ending c, h, s */ + unsigned int lbc, lbh, lbs; /* logical beginning c, h, s */ + unsigned int lec, leh, les; /* logical ending c, h, s */ + + if (!is_dos_compatible(cxt)) + return; + + if (!cxt->geom.heads || !cxt->geom.sectors || (partition >= 4)) + return; /* do not check extended partitions */ + +/* physical beginning c, h, s */ + pbc = (p->bc & 0xff) | ((p->bs << 2) & 0x300); + pbh = p->bh; + pbs = p->bs & 0x3f; + +/* physical ending c, h, s */ + pec = (p->ec & 0xff) | ((p->es << 2) & 0x300); + peh = p->eh; + pes = p->es & 0x3f; + +/* compute logical beginning (c, h, s) */ + long2chs(cxt, dos_partition_get_start(p), &lbc, &lbh, &lbs); + +/* compute logical ending (c, h, s) */ + long2chs(cxt, dos_partition_get_start(p) + dos_partition_get_size(p) - 1, &lec, &leh, &les); + +/* Same physical / logical beginning? */ + if (cxt->geom.cylinders <= 1024 + && (pbc != lbc || pbh != lbh || pbs != lbs)) { + fdisk_warnx(cxt, _("Partition %zd: different physical/logical " + "beginnings (non-Linux?): " + "phys=(%d, %d, %d), logical=(%d, %d, %d)"), + partition + 1, + pbc, pbh, pbs, + lbc, lbh, lbs); + } + +/* Same physical / logical ending? */ + if (cxt->geom.cylinders <= 1024 + && (pec != lec || peh != leh || pes != les)) { + fdisk_warnx(cxt, _("Partition %zd: different physical/logical " + "endings: phys=(%d, %d, %d), logical=(%d, %d, %d)"), + partition + 1, + pec, peh, pes, + lec, leh, les); + } + +/* Ending on cylinder boundary? */ + if (peh != (cxt->geom.heads - 1) || pes != cxt->geom.sectors) { + fdisk_warnx(cxt, _("Partition %zd: does not end on " + "cylinder boundary."), + partition + 1); + } +} + +static int dos_verify_disklabel(struct fdisk_context *cxt) +{ + size_t i, j; + sector_t total = 1, n_sectors = cxt->total_sectors; + unsigned long long first[cxt->label->nparts_max], + last[cxt->label->nparts_max]; + struct dos_partition *p; + struct fdisk_dos_label *l = self_label(cxt); + + assert(fdisk_is_disklabel(cxt, DOS)); + + fill_bounds(cxt, first, last); + for (i = 0; i < cxt->label->nparts_max; i++) { + struct pte *pe = self_pte(cxt, i); + + p = self_partition(cxt, i); + if (p->sys_ind && !IS_EXTENDED(p->sys_ind)) { + check_consistency(cxt, p, i); + fdisk_warn_alignment(cxt, get_abs_partition_start(pe), i); + if (get_abs_partition_start(pe) < first[i]) + fdisk_warnx(cxt, _( + "Partition %zd: bad start-of-data."), + i + 1); + + check(cxt, i + 1, p->eh, p->es, p->ec, 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]))) { + + fdisk_warnx(cxt, _("Partition %zd: " + "overlaps partition %zd."), + j + 1, i + 1); + + total += first[i] >= first[j] ? + first[i] : first[j]; + total -= last[i] <= last[j] ? + last[i] : last[j]; + } + } + } + } + + if (l->ext_offset) { + p = self_partition(cxt, l->ext_index); + + sector_t e_last = dos_partition_get_start(p) + + dos_partition_get_size(p) - 1; + + for (i = 4; i < cxt->label->nparts_max; i++) { + total++; + p = self_partition(cxt, i); + + if (!p->sys_ind) { + if (i != 4 || i + 1 < cxt->label->nparts_max) + fdisk_warnx(cxt, + _("Partition %zd: empty"), + i + 1); + } else if (first[i] < l->ext_offset + || last[i] > e_last) { + + fdisk_warnx(cxt, _("Logical partition %zd: " + "not entirely in partition %zd"), + i + 1, l->ext_index + 1); + } + } + } + + if (total > n_sectors) + fdisk_warnx(cxt, _("Total allocated sectors %llu greater " + "than the maximum %llu."), total, n_sectors); + else if (total < n_sectors) + fdisk_warnx(cxt, _("Remaining %lld unallocated %ld-byte " + "sectors"), n_sectors - total, cxt->sector_size); + + return 0; +} + +/* + * Ask the user for new partition type information (logical, extended). + * This function calls the actual partition adding logic - add_partition. + * + * API callback. + */ +static int dos_add_partition( + struct fdisk_context *cxt, + size_t partnum __attribute__ ((__unused__)), + struct fdisk_parttype *t) +{ + size_t i, free_primary = 0; + int rc = 0; + struct fdisk_dos_label *l = self_label(cxt); + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_disklabel(cxt, DOS)); + + for (i = 0; i < 4; i++) { + struct dos_partition *p = self_partition(cxt, i); + free_primary += !p->sys_ind; + } + + if (!free_primary && cxt->label->nparts_max >= MAXIMUM_PARTS) { + fdisk_info(cxt, _("The maximum number of partitions has " + "been created.")); + return -EINVAL; + } + rc = 1; + + if (!free_primary) { + if (l->ext_offset) { + fdisk_info(cxt, _("All primary partitions are in use.")); + rc = add_logical(cxt); + } else + fdisk_info(cxt, _("If you want to create more than " + "four partitions, you must replace a" + "primary partition with an extended " + "partition first.")); + + } else if (cxt->label->nparts_max >= MAXIMUM_PARTS) { + int j; + + fdisk_info(cxt, _("All logical partitions are in use. " + "Adding a primary partition.")); + j = get_partition_unused_primary(cxt); + if (j >= 0) + rc = add_partition(cxt, j, t); + } else { + char *buf; + char c, prompt[BUFSIZ]; + int dflt; + + dflt = (free_primary == 1 && !l->ext_offset) ? 'e' : 'p'; + + snprintf(prompt, sizeof(prompt), + _("Partition type:\n" + " p primary (%zd primary, %d extended, %zd free)\n" + "%s\n" + "Select (default %c)"), + 4 - (l->ext_offset ? 1 : 0) - free_primary, + l->ext_offset ? 1 : 0, free_primary, + l->ext_offset ? _(" l logical (numbered from 5)") : _(" e extended"), + dflt); + + rc = fdisk_ask_string(cxt, prompt, &buf); + if (rc) + return rc; + if (!buf[0]) { + c = dflt; + fdisk_info(cxt, _("Using default response %c"), c); + } else + c = tolower(buf[0]); + free(buf); + + if (c == 'p') { + int j = get_partition_unused_primary(cxt); + if (j >= 0) + rc = add_partition(cxt, j, t); + goto done; + } else if (c == 'l' && l->ext_offset) { + rc = add_logical(cxt); + goto done; + } else if (c == 'e' && !l->ext_offset) { + int j = get_partition_unused_primary(cxt); + if (j >= 0) { + t = fdisk_get_parttype_from_code(cxt, + MBR_DOS_EXTENDED_PARTITION); + rc = add_partition(cxt, j, t); + } + goto done; + } else + fdisk_warnx(cxt, _("Invalid partition type `%c'"), c); + } +done: + if (rc == 0) + cxt->label->nparts_cur++; + return rc; +} + +static int write_sector(struct fdisk_context *cxt, sector_t secno, + unsigned char *buf) +{ + int rc; + + rc = seek_sector(cxt, secno); + if (rc != 0) { + fdisk_warn(cxt, _("Write sector %jd failed: seek failed"), + (uintmax_t) secno); + return rc; + } + if (write(cxt->dev_fd, buf, cxt->sector_size) != (ssize_t) cxt->sector_size) + return -errno; + return 0; +} + +static int dos_write_disklabel(struct fdisk_context *cxt) +{ + struct fdisk_dos_label *l = self_label(cxt); + size_t i; + int rc = 0, mbr_changed = 0; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_disklabel(cxt, DOS)); + + mbr_changed = l->non_pt_changed; + + /* MBR (primary partitions) */ + if (!mbr_changed) { + for (i = 0; i < 4; i++) { + struct pte *pe = self_pte(cxt, i); + if (pe->changed) + mbr_changed = 1; + } + } + if (mbr_changed) { + mbr_set_magic(cxt->firstsector); + rc = write_sector(cxt, 0, cxt->firstsector); + if (rc) + goto done; + } + + /* EBR (logical partitions) */ + for (i = 4; i < cxt->label->nparts_max; i++) { + struct pte *pe = self_pte(cxt, i); + + if (pe->changed) { + mbr_set_magic(pe->sectorbuffer); + rc = write_sector(cxt, pe->offset, pe->sectorbuffer); + if (rc) + goto done; + } + } + +done: + return rc; +} + +static struct fdisk_parttype *dos_get_parttype( + struct fdisk_context *cxt, + size_t partnum) +{ + struct fdisk_parttype *t; + struct dos_partition *p; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_disklabel(cxt, DOS)); + + if (partnum >= cxt->label->nparts_max) + return NULL; + + p = self_partition(cxt, partnum); + t = fdisk_get_parttype_from_code(cxt, p->sys_ind); + if (!t) + t = fdisk_new_unknown_parttype(p->sys_ind, NULL); + return t; +} + +static int dos_set_parttype( + struct fdisk_context *cxt, + size_t partnum, + struct fdisk_parttype *t) +{ + struct dos_partition *p; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_disklabel(cxt, DOS)); + + if (partnum >= cxt->label->nparts_max || !t || t->type > UINT8_MAX) + return -EINVAL; + + p = self_partition(cxt, partnum); + if (t->type == p->sys_ind) + return 0; + + if (IS_EXTENDED(p->sys_ind) || IS_EXTENDED(t->type)) { + fdisk_warnx(cxt, _("You cannot change a partition into an " + "extended one or vice versa. Delete it first.")); + return -EINVAL; + } + + if (is_dos_partition(t->type) || is_dos_partition(p->sys_ind)) + fdisk_info(cxt, _("If you have created or modified any DOS 6.x " + "partitions, please see the fdisk documantation for additional " + "information.")); + + p->sys_ind = t->type; + + partition_set_changed(cxt, partnum, 1); + return 0; +} + +/* + * Check whether partition entries are ordered by their starting positions. + * Return 0 if OK. Return i if partition i should have been earlier. + * Two separate checks: primary and logical partitions. + */ +static int wrong_p_order(struct fdisk_context *cxt, size_t *prev) +{ + size_t last_p_start_pos = 0, p_start_pos; + size_t i, last_i = 0; + + for (i = 0 ; i < cxt->label->nparts_max; i++) { + + struct pte *pe = self_pte(cxt, i); + struct dos_partition *p = pe->pt_entry; + + if (i == 4) { + last_i = 4; + last_p_start_pos = 0; + } + if (p->sys_ind) { + p_start_pos = get_abs_partition_start(pe); + + if (last_p_start_pos > p_start_pos) { + if (prev) + *prev = last_i; + return i; + } + + last_p_start_pos = p_start_pos; + last_i = i; + } + } + return 0; +} + +static int is_garbage_table(struct fdisk_context *cxt) +{ + size_t i; + + for (i = 0; i < 4; i++) { + struct dos_partition *p = self_partition(cxt, i); + + if (p->boot_ind != 0 && p->boot_ind != 0x80) + return 1; + } + return 0; +} + +/* + * List all PT fields. + * + * This is useful for PT debugging (or for 70's Hippies + * who are on permanent LSD trip). + */ +static int dos_fulllist_disklabel(struct fdisk_context *cxt, int ext) +{ + int rc; + size_t i; + struct tt *tb = NULL; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_disklabel(cxt, DOS)); + + tb = tt_new_table(TT_FL_FREEDATA); + if (!tb) + return -ENOMEM; + + tt_define_column(tb, _("Nr"), 2, TT_FL_RIGHT); + tt_define_column(tb, _("AF"), 2, TT_FL_RIGHT); + + tt_define_column(tb, _("Hd"), 4, TT_FL_RIGHT); + tt_define_column(tb, _("Sec"), 4, TT_FL_RIGHT); + tt_define_column(tb, _("Cyl"), 5, TT_FL_RIGHT); + + tt_define_column(tb, _("Hd"), 4, TT_FL_RIGHT); + tt_define_column(tb, _("Sec"), 4, TT_FL_RIGHT); + tt_define_column(tb, _("Cyl"), 5, TT_FL_RIGHT); + + tt_define_column(tb, _("Start"), 9, TT_FL_RIGHT); + tt_define_column(tb, _("Size"), 9, TT_FL_RIGHT); + tt_define_column(tb, _("Id"), 2, TT_FL_RIGHT); + + for (i = 0 ; i < cxt->label->nparts_max; i++) { + struct pte *pe = self_pte(cxt, i); + struct dos_partition *p; + struct tt_line *ln; + char *str; + + p = ext ? pe->ex_entry : pe->pt_entry; + if (!p) + continue; + ln = tt_add_line(tb, NULL); + if (!ln) + continue; + + if (asprintf(&str, "%zd", i + 1) > 0) + tt_line_set_data(ln, 0, str); /* Nr */ + if (asprintf(&str, "%02x", p->boot_ind) > 0) + tt_line_set_data(ln, 1, str); /* AF */ + + if (asprintf(&str, "%d", p->bh) > 0) + tt_line_set_data(ln, 2, str); /* Hd */ + if (asprintf(&str, "%d", sector(p->bs)) > 0) + tt_line_set_data(ln, 3, str); /* Sec */ + if (asprintf(&str, "%d", cylinder(p->bs, p->bc)) > 0) + tt_line_set_data(ln, 4, str); /* Cyl */ + + if (asprintf(&str, "%d", p->eh) > 0) + tt_line_set_data(ln, 5, str); /* Hd */ + if (asprintf(&str, "%d", sector(p->es)) > 0) + tt_line_set_data(ln, 6, str); /* Sec */ + if (asprintf(&str, "%d", cylinder(p->es, p->ec)) > 0) + tt_line_set_data(ln, 7, str); /* Cyl */ + + if (asprintf(&str, "%lu", + (unsigned long) dos_partition_get_start(p)) > 0) + tt_line_set_data(ln, 8, str); /* Start */ + if (asprintf(&str, "%lu", + (unsigned long) dos_partition_get_size(p)) > 0) + tt_line_set_data(ln, 9, str); /* End */ + + if (asprintf(&str, "%02x", p->sys_ind) > 0) + tt_line_set_data(ln, 10, str); /* Id */ + + if (p->sys_ind) { + check_consistency(cxt, p, i); + fdisk_warn_alignment(cxt, + get_abs_partition_start(pe), i); + } + } + + rc = fdisk_print_table(cxt, tb); + tt_free_table(tb); + + return rc; +} + +int fdisk_dos_list_extended(struct fdisk_context *cxt) +{ + return dos_fulllist_disklabel(cxt, 1); +} + +static int dos_list_disklabel(struct fdisk_context *cxt) +{ + int rc = 0, trunc = TT_FL_TRUNC; + size_t i; + struct tt *tb = NULL; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_disklabel(cxt, DOS)); + + if (is_garbage_table(cxt)) { + fdisk_warnx(cxt, _( + "This doesn't look like a partition table " + "Probably you selected the wrong device.")); + } + + if (fdisk_context_display_details(cxt)) + return dos_fulllist_disklabel(cxt, 0); + + tb = tt_new_table(TT_FL_FREEDATA); + if (!tb) + return -ENOMEM; + + /* don't trunc anything in expert mode */ + if (fdisk_context_display_details(cxt)) + trunc = 0; + + tt_define_column(tb, _("Device"), 0.1, 0); + tt_define_column(tb, _("Boot"), 1, 0); + tt_define_column(tb, _("Start"), 9, TT_FL_RIGHT); + tt_define_column(tb, _("End"), 9, TT_FL_RIGHT); + /* TRANSLATORS: keep one blank space behind 'Blocks' */ + tt_define_column(tb, _("Blocks "), 5, TT_FL_RIGHT); + tt_define_column(tb, _("Id"), 2, TT_FL_RIGHT); + tt_define_column(tb, _("System"), 0.1, trunc); + + for (i = 0; i < cxt->label->nparts_max; i++) { + struct pte *pe = self_pte(cxt, i); + struct dos_partition *p = pe->pt_entry; + unsigned int psects, pblocks, podd = 0;; + struct fdisk_parttype *type; + struct tt_line *ln; + char *str; + + if (!p || is_cleared_partition(p)) + continue; + ln = tt_add_line(tb, NULL); + if (!ln) + continue; + + pblocks = psects = dos_partition_get_size(p); + type = fdisk_get_parttype_from_code(cxt, p->sys_ind); + + if (cxt->sector_size < 1024) { + pblocks /= (1024 / cxt->sector_size); + podd = psects % (1024 / cxt->sector_size); + } + if (cxt->sector_size > 1024) + pblocks *= (cxt->sector_size / 1024); + + str = fdisk_partname(cxt->dev_path, i + 1); + if (str) + tt_line_set_data(ln, 0, str); /* device */ + + str = strdup(p->boot_ind ? + p->boot_ind == ACTIVE_FLAG ? "*" : "?" : " "); + if (str) + tt_line_set_data(ln, 1, str); /* boot flag */ + + if (asprintf(&str, "%lu", (unsigned long) cround(cxt, + get_abs_partition_start(pe))) > 0) + tt_line_set_data(ln, 2, str); /* start */ + + if (asprintf(&str, "%lu", (unsigned long) cround(cxt, + get_abs_partition_start(pe) + + psects - (psects ? 1 : 0))) > 0) + tt_line_set_data(ln, 3, str); /* end */ + + if (asprintf(&str, "%lu%c", (unsigned long) pblocks, + podd ? '+' : ' ') > 0) + tt_line_set_data(ln, 4, str); /* blocks<flag> */ + + if (asprintf(&str, "%x", p->sys_ind) > 0) + tt_line_set_data(ln, 5, str); /* id */ + + str = strdup(type ? type->name : _("Unknown")); + if (str) + tt_line_set_data(ln, 6, str); + + check_consistency(cxt, p, i); + fdisk_warn_alignment(cxt, get_abs_partition_start(pe), i); + fdisk_free_parttype(type); + } + + rc = fdisk_print_table(cxt, tb); + tt_free_table(tb); + + /* Is partition table in disk order? It need not be, but... */ + /* partition table entries are not checked for correct order if this + is a sgi, sun labeled disk... */ + if (wrong_p_order(cxt, NULL)) + fdisk_info(cxt, _("Partition table entries are not in " + "disk order.")); + + return rc; +} + + +/* + * Fix the chain of logicals. + * ext_offset is unchanged, the set of sectors used is unchanged + * The chain is sorted so that sectors increase, and so that + * starting sectors increase. + * + * After this it may still be that cfdisk doesn't like the table. + * (This is because cfdisk considers expanded parts, from link to + * end of partition, and these may still overlap.) + * Now + * sfdisk /dev/hda > ohda; sfdisk /dev/hda < ohda + * may help. + */ +static void fix_chain_of_logicals(struct fdisk_context *cxt) +{ + struct fdisk_dos_label *l = self_label(cxt); + size_t j, oj, ojj, sj, sjj; + struct dos_partition *pj,*pjj,tmp; + + /* Stage 1: sort sectors but leave sector of part 4 */ + /* (Its sector is the global ext_offset.) */ + stage1: + for (j = 5; j < cxt->label->nparts_max - 1; j++) { + oj = l->ptes[j].offset; + ojj = l->ptes[j + 1].offset; + if (oj > ojj) { + l->ptes[j].offset = ojj; + l->ptes[j + 1].offset = oj; + pj = l->ptes[j].pt_entry; + dos_partition_set_start(pj, dos_partition_get_start(pj)+oj-ojj); + pjj = l->ptes[j + 1].pt_entry; + dos_partition_set_start(pjj, dos_partition_get_start(pjj)+ojj-oj); + dos_partition_set_start(l->ptes[j - 1].ex_entry, + ojj - l->ext_offset); + dos_partition_set_start(l->ptes[j].ex_entry, + oj - l->ext_offset); + goto stage1; + } + } + + /* Stage 2: sort starting sectors */ + stage2: + for (j = 4; j < cxt->label->nparts_max - 1; j++) { + pj = l->ptes[j].pt_entry; + pjj = l->ptes[j + 1].pt_entry; + sj = dos_partition_get_start(pj); + sjj = dos_partition_get_start(pjj); + oj = l->ptes[j].offset; + ojj = l->ptes[j+1].offset; + if (oj+sj > ojj+sjj) { + tmp = *pj; + *pj = *pjj; + *pjj = tmp; + dos_partition_set_start(pj, ojj+sjj-oj); + dos_partition_set_start(pjj, oj+sj-ojj); + goto stage2; + } + } + + /* Probably something was changed */ + for (j = 4; j < cxt->label->nparts_max; j++) + l->ptes[j].changed = 1; +} + +int fdisk_dos_fix_order(struct fdisk_context *cxt) +{ + struct pte *pei, *pek; + size_t i,k; + + if (!wrong_p_order(cxt, NULL)) { + fdisk_info(cxt, _("Nothing to do. Ordering is correct already.")); + return 0; + } + + while ((i = wrong_p_order(cxt, &k)) != 0 && i < 4) { + /* partition i should have come earlier, move it */ + /* We have to move data in the MBR */ + struct dos_partition *pi, *pk, *pe, pbuf; + pei = self_pte(cxt, i); + pek = self_pte(cxt, k); + + pe = pei->ex_entry; + pei->ex_entry = pek->ex_entry; + pek->ex_entry = pe; + + pi = pei->pt_entry; + pk = pek->pt_entry; + + memmove(&pbuf, pi, sizeof(struct dos_partition)); + memmove(pi, pk, sizeof(struct dos_partition)); + memmove(pk, &pbuf, sizeof(struct dos_partition)); + + partition_set_changed(cxt, i, 1); + partition_set_changed(cxt, k, 1); + } + + if (i) + fix_chain_of_logicals(cxt); + + fdisk_info(cxt, _("Done.")); + return 0; +} + +int fdisk_dos_move_begin(struct fdisk_context *cxt, int i) +{ + struct pte *pe; + struct dos_partition *p; + unsigned int new, free_start, curr_start, last; + uintmax_t res = 0; + size_t x; + int rc; + + assert(cxt); + assert(fdisk_is_disklabel(cxt, DOS)); + + pe = self_pte(cxt, i); + p = pe->pt_entry; + + if (!p->sys_ind || !dos_partition_get_size(p) || IS_EXTENDED (p->sys_ind)) { + fdisk_warn(cxt, _("Partition %d: no data area."), i + 1); + return 0; + } + + /* the default start is at the second sector of the disk or at the + * second sector of the extended partition + */ + free_start = pe->offset ? pe->offset + 1 : 1; + + curr_start = get_abs_partition_start(pe); + + /* look for a free space before the current start of the partition */ + for (x = 0; x < cxt->label->nparts_max; x++) { + unsigned int end; + struct pte *prev_pe = self_pte(cxt, x); + struct dos_partition *prev_p = prev_pe->pt_entry; + + if (!prev_p) + continue; + end = get_abs_partition_start(prev_pe) + + dos_partition_get_size(prev_p); + + if (!is_cleared_partition(prev_p) && + end > free_start && end <= curr_start) + free_start = end; + } + + last = get_abs_partition_start(pe) + dos_partition_get_size(p) - 1; + + rc = fdisk_ask_number(cxt, free_start, curr_start, last, + _("New beginning of data"), &res); + if (rc) + return rc; + + new = res - pe->offset; + + if (new != dos_partition_get_size(p)) { + unsigned int sects = dos_partition_get_size(p) + + dos_partition_get_start(p) - new; + + dos_partition_set_size(p, sects); + dos_partition_set_start(p, new); + + partition_set_changed(cxt, i, 1); + } + + return rc; +} + +static int dos_get_partition_status( + struct fdisk_context *cxt, + size_t i, + int *status) +{ + struct dos_partition *p; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_disklabel(cxt, DOS)); + + if (!status || i >= cxt->label->nparts_max) + return -EINVAL; + + p = self_partition(cxt, i); + + if (p && !is_cleared_partition(p)) + *status = FDISK_PARTSTAT_USED; + else + *status = FDISK_PARTSTAT_NONE; + + return 0; +} + +static int dos_toggle_partition_flag( + struct fdisk_context *cxt, + size_t i, + unsigned long flag) +{ + struct dos_partition *p; + + assert(cxt); + assert(cxt->label); + assert(fdisk_is_disklabel(cxt, DOS)); + + if (i >= cxt->label->nparts_max) + return -EINVAL; + + p = self_partition(cxt, i); + + switch (flag) { + case DOS_FLAG_ACTIVE: + if (IS_EXTENDED(p->sys_ind) && !p->boot_ind) + fdisk_warnx(cxt, _("Partition %d: is an extended " + "partition."), i + 1); + + p->boot_ind = (p->boot_ind ? 0 : ACTIVE_FLAG); + partition_set_changed(cxt, i, 1); + break; + default: + return 1; + } + + return 0; +} + +static const struct fdisk_label_operations dos_operations = +{ + .probe = dos_probe_label, + .write = dos_write_disklabel, + .verify = dos_verify_disklabel, + .create = dos_create_disklabel, + .list = dos_list_disklabel, + .get_id = dos_get_disklabel_id, + .set_id = dos_set_disklabel_id, + + .part_add = dos_add_partition, + .part_delete = dos_delete_partition, + .part_get_type = dos_get_parttype, + .part_set_type = dos_set_parttype, + + .part_toggle_flag = dos_toggle_partition_flag, + .part_get_status = dos_get_partition_status, + + .reset_alignment = dos_reset_alignment, + + .deinit = dos_deinit, +}; + +/* + * allocates DOS in-memory stuff + */ +struct fdisk_label *fdisk_new_dos_label(struct fdisk_context *cxt) +{ + struct fdisk_label *lb; + struct fdisk_dos_label *dos; + + assert(cxt); + + dos = calloc(1, sizeof(*dos)); + if (!dos) + return NULL; + + /* initialize generic part of the driver */ + lb = (struct fdisk_label *) dos; + lb->name = "dos"; + lb->id = FDISK_DISKLABEL_DOS; + lb->op = &dos_operations; + lb->parttypes = dos_parttypes; + lb->nparttypes = ARRAY_SIZE(dos_parttypes); + + lb->flags |= FDISK_LABEL_FL_ADDPART_NOPARTNO; + + return lb; +} + +/* + * Public label specific functions + */ + +int fdisk_dos_enable_compatible(struct fdisk_label *lb, int enable) +{ + struct fdisk_dos_label *dos = (struct fdisk_dos_label *) lb; + + if (!lb) + return -EINVAL; + + dos->compatible = enable; + if (enable) + lb->flags |= FDISK_LABEL_FL_REQUIRE_GEOMETRY; + return 0; +} + +int fdisk_dos_is_compatible(struct fdisk_label *lb) +{ + return ((struct fdisk_dos_label *) lb)->compatible; +} |