Update blkid to 2.25.0
Break libblkid into 4 libraries: libblkid, libuuid, libutil-linux and libfdisk.

This should help in later patch updates.

Change-Id: I680d9a7feb031e5c29a603e9c58aff4b65826262
diff --git a/libblkid/libfdisk/src/dos.c b/libblkid/libfdisk/src/dos.c
new file mode 100644
index 0000000..2a06707
--- /dev/null
+++ b/libblkid/libfdisk/src/dos.c
@@ -0,0 +1,2331 @@
+/*
+ *
+ * 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 "strutils.h"
+
+#include "fdiskP.h"
+
+#include <ctype.h>
+
+#define MAXIMUM_PARTS	60
+#define ACTIVE_FLAG     0x80
+
+/**
+ * SECTION: dos
+ * @title: DOS (MBR)
+ * @short_description: disk label specific functions
+ *
+ */
+
+
+#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 */
+	fdisk_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 */
+	fdisk_sector_t	ext_offset;		/* start of the ext.partition */
+	size_t		ext_index;		/* ext.partition index (if ext_offset is set) */
+	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_label(_x, DOS) && \
+                    fdisk_dos_is_compatible(fdisk_get_label(_x, NULL)))
+
+#define cround(c, n)	fdisk_cround(c, n)
+
+
+static inline struct fdisk_dos_label *self_label(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(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)
+{
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	return self_partition(cxt, i);
+}
+
+static struct fdisk_parttype *dos_partition_parttype(
+		struct fdisk_context *cxt,
+		struct dos_partition *p)
+{
+	struct fdisk_parttype *t
+		= fdisk_label_get_parttype_from_code(cxt->label, p->sys_ind);
+	return t ? : fdisk_new_unknown_parttype(p->sys_ind, NULL);
+}
+
+/*
+ * Linux kernel cares about partition size only. Things like
+ * partition type or so are completely irrelevant -- kzak Nov-2013
+ */
+static int is_used_partition(struct dos_partition *p)
+{
+	return p && dos_partition_get_size(p) != 0;
+}
+
+static void partition_set_changed(
+				struct fdisk_context *cxt,
+				size_t i,
+				int changed)
+{
+	struct pte *pe = self_pte(cxt, i);
+
+	if (!pe)
+		return;
+
+	DBG(LABEL, ul_debug("DOS: setting %zu partition to %s", i,
+				changed ? "changed" : "unchanged"));
+
+	pe->changed = changed ? 1 : 0;
+	if (changed)
+		fdisk_label_set_changed(cxt->label, 1);
+}
+
+static fdisk_sector_t get_abs_partition_start(struct pte *pe)
+{
+	assert(pe);
+	assert(pe->pt_entry);
+
+	return pe->offset + dos_partition_get_start(pe->pt_entry);
+}
+
+static fdisk_sector_t get_abs_partition_end(struct pte *pe)
+{
+	fdisk_sector_t size;
+
+	assert(pe);
+	assert(pe->pt_entry);
+
+	size = dos_partition_get_size(pe->pt_entry);
+	return get_abs_partition_start(pe) + size - (size ? 1 : 0);
+}
+
+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,
+					struct fdisk_partition *pa,
+					size_t *partno)
+{
+	size_t org, n;
+	int rc;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(partno);
+
+	org = cxt->label->nparts_max;
+
+	cxt->label->nparts_max = 4;
+	rc = fdisk_partition_next_partno(pa, cxt, &n);
+	cxt->label->nparts_max = org;
+
+	if (rc == 1) {
+		fdisk_info(cxt, _("All primary partitions have been defined already."));
+		rc = -1;
+	} else if (rc == 0)
+		*partno = n;
+	return rc;
+}
+
+static int seek_sector(struct fdisk_context *cxt, fdisk_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, fdisk_sector_t secno,
+			unsigned char *buf)
+{
+	int rc = seek_sector(cxt, secno);
+	ssize_t r;
+
+	if (rc < 0)
+		return rc;
+
+	r = read(cxt->dev_fd, buf, cxt->sector_size);
+	if (r == (ssize_t) cxt->sector_size)
+		return 0;
+	if (r < 0)
+		return -errno;
+	return -1;
+}
+
+/* Allocate a buffer and read a partition table sector */
+static int read_pte(struct fdisk_context *cxt, size_t pno, fdisk_sector_t offset)
+{
+	int rc;
+	unsigned char *buf;
+	struct pte *pe = self_pte(cxt, pno);
+
+	buf = calloc(1, cxt->sector_size);
+	if (!buf)
+		return -ENOMEM;
+
+	DBG(LABEL, ul_debug("DOS: reading EBR %zu: offset=%ju, buffer=%p",
+				pno, (uintmax_t) offset, buf));
+
+	pe->offset = offset;
+	pe->sectorbuffer = buf;
+	pe->private_sectorbuffer = 1;
+
+	rc = read_sector(cxt, offset, pe->sectorbuffer);
+	if (rc) {
+		fdisk_warn(cxt, _("Failed to read extended partition table "
+				"(offset=%ju)"), (uintmax_t) offset);
+		return rc;
+	}
+
+	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;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	DBG(LABEL, ul_debug("DOS: initialize, first sector buffer %p", cxt->firstsector));
+
+	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->private_sectorbuffer = 0;
+		pe->changed = 0;
+	}
+
+	if (fdisk_is_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_use_cylinders(cxt))
+		fdisk_warnx(cxt, _("Cylinders as display units are deprecated."));
+
+	if (cxt->total_sectors > UINT_MAX) {
+		uint64_t bytes = cxt->total_sectors * cxt->sector_size;
+		char *szstr = size_to_human_string(SIZE_SUFFIX_SPACE
+					   | SIZE_SUFFIX_3LETTER, bytes);
+		fdisk_warnx(cxt,
+		_("The size of this disk is %s (%ju bytes). DOS "
+		  "partition table format can not be used on drives for "
+		  "volumes larger than %lu bytes for %lu-byte "
+		  "sectors. Use GUID partition table format (GPT)."),
+			szstr, bytes,
+			UINT_MAX * cxt->sector_size,
+			cxt->sector_size);
+		free(szstr);
+	}
+}
+
+/* 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 && pe->sectorbuffer) {
+			DBG(LABEL, ul_debug("DOS: freeing pte %zu sector buffer %p",
+						i, pe->sectorbuffer));
+			free(pe->sectorbuffer);
+		}
+		pe->sectorbuffer = NULL;
+		pe->private_sectorbuffer = 0;
+	}
+
+	memset(l->ptes, 0, sizeof(l->ptes));
+}
+
+static void reset_pte(struct pte *pe)
+{
+	assert(pe);
+
+	if (pe->private_sectorbuffer) {
+		DBG(LABEL, ul_debug("  --> freeing pte sector buffer %p",
+					pe->sectorbuffer));
+		free(pe->sectorbuffer);
+	}
+	memset(pe, 0, sizeof(struct pte));
+}
+
+static int dos_delete_partition(struct fdisk_context *cxt, size_t partnum)
+{
+	struct fdisk_dos_label *l;
+	struct pte *pe;
+	struct dos_partition *p;
+	struct dos_partition *q;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	pe = self_pte(cxt, partnum);
+	if (!pe)
+		return -EINVAL;
+
+	DBG(LABEL, ul_debug("DOS: delete partiton %zu (max=%zu)", partnum,
+				cxt->label->nparts_max));
+
+	l = self_label(cxt);
+	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) {
+		DBG(LABEL, ul_debug("--> delete primary"));
+		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;
+			l->ext_index = 0;
+		}
+		partition_set_changed(cxt, partnum, 1);
+		clear_partition(p);
+	} else if (!q->sys_ind && partnum > 4) {
+		DBG(LABEL, ul_debug("--> delete logical [last in the chain]"));
+		reset_pte(&l->ptes[partnum]);
+		--cxt->label->nparts_max;
+		--partnum;
+		/* clear link to deleted partition */
+		clear_partition(l->ptes[partnum].ex_entry);
+		partition_set_changed(cxt, partnum, 1);
+	} else {
+		DBG(LABEL, ul_debug("--> delete logical [move down]"));
+		if (partnum > 4) {
+			DBG(LABEL, ul_debug(" --> delete %zu logical link", partnum));
+			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) {
+			DBG(LABEL, ul_debug(" --> delete first logical link"));
+			pe = &l->ptes[5];	/* second logical */
+
+			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) {
+			DBG(LABEL, ul_debug(" --> move ptes"));
+			cxt->label->nparts_max--;
+			reset_pte(&l->ptes[partnum]);
+			while (partnum < cxt->label->nparts_max) {
+				DBG(LABEL, ul_debug("  --> moving pte %zu <-- %zu", partnum, partnum + 1));
+				l->ptes[partnum] = l->ptes[partnum + 1];
+				partnum++;
+			}
+			memset(&l->ptes[partnum], 0, sizeof(struct pte));
+		} else {
+			DBG(LABEL, ul_debug(" --> the only logical: clear only"));
+			clear_partition(l->ptes[partnum].pt_entry);
+			cxt->label->nparts_max--;
+
+			if (partnum == 4) {
+				DBG(LABEL, ul_debug("  --> clear last logical"));
+				reset_pte(&l->ptes[partnum]);
+				partition_set_changed(cxt, l->ext_index, 1);
+			}
+		}
+	}
+
+	fdisk_label_set_changed(cxt->label, 1);
+	return 0;
+}
+
+static void read_extended(struct fdisk_context *cxt, size_t ext)
+{
+	size_t i;
+	struct pte *pex, *pe;
+	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;
+	}
+
+	DBG(LABEL, ul_debug("DOS: Reading extended %zu", ext));
+
+	while (IS_EXTENDED (p->sys_ind)) {
+		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 #%zu. 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;
+		}
+
+		if (read_pte(cxt, cxt->label->nparts_max, l->ext_offset +
+						dos_partition_get_start(p)))
+			return;
+
+		if (!l->ext_offset)
+			l->ext_offset = dos_partition_get_start(p);
+
+		assert(pe->sectorbuffer);
+		q = p = mbr_get_partition(pe->sectorbuffer, 0);
+
+		for (i = 0; i < 4; i++, p++) {
+			if (!dos_partition_get_size(p))
+				continue;
+
+			if (IS_EXTENDED (p->sys_ind)) {
+				if (pe->ex_entry)
+					fdisk_warnx(cxt, _(
+					"Extra link pointer in partition "
+					"table %zu."),
+						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 %zu."),
+						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;
+
+		DBG(LABEL, ul_debug("DOS: EBR[offset=%ju]: link: type=%x,  start=%u, size=%u; "
+				                         " data: type=%x, start=%u, size=%u",
+				    (uintmax_t) pe->offset,
+				    pe->ex_entry->sys_ind,
+				    dos_partition_get_start(pe->ex_entry),
+				    dos_partition_get_size(pe->ex_entry),
+				    pe->pt_entry->sys_ind,
+				    dos_partition_get_start(pe->pt_entry),
+				    dos_partition_get_size(pe->pt_entry)));
+
+	}
+
+	/* remove last empty EBR */
+	pe = self_pte(cxt, cxt->label->nparts_max - 1);
+	if (is_cleared_partition(pe->ex_entry) &&
+	    is_cleared_partition(pe->pt_entry)) {
+		DBG(LABEL, ul_debug("DOS: EBR[offset=%ju]: empty, remove", (uintmax_t) pe->offset));
+		reset_pte(pe);
+		cxt->label->nparts_max--;
+		cxt->label->nparts_cur--;
+	}
+
+	/* 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 (%zu)"), i+1);
+			dos_delete_partition(cxt, i);
+			goto remove; 	/* numbering changed */
+		}
+	}
+
+	DBG(LABEL, ul_debug("DOS: nparts_max: %zu", cxt->label->nparts_max));
+}
+
+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_label(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 = 0;
+	int rc, has_id = 0;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	DBG(LABEL, ul_debug("DOS: creating new disklabel"));
+
+	if (cxt->script) {
+		char *end = NULL;
+		const char *s = fdisk_script_get_header(cxt->script, "label-id");
+
+		if (s) {
+			errno = 0;
+			id = strtol(s, &end, 16);
+			if (!errno && end && s < end)
+				has_id = 1;
+		}
+	}
+
+	/* random disk signature */
+	if (!has_id)
+		random_get_bytes(&id, sizeof(id));
+
+	dos_init(cxt);
+	rc = fdisk_init_firstsector_buffer(cxt);
+	if (rc)
+		return rc;
+	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);
+
+	fdisk_info(cxt, _("Created a new DOS disklabel with disk "
+			 "identifier 0x%08x."), id);
+	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_label(cxt, DOS));
+
+	DBG(LABEL, ul_debug("DOS: setting Id"));
+
+	l = self_label(cxt);
+	old = mbr_get_id(cxt->firstsector);
+	rc = fdisk_ask_string(cxt,
+			_("Enter 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;
+	}
+
+
+	mbr_set_id(cxt->firstsector, id);
+	l->non_pt_changed = 1;
+	fdisk_label_set_changed(cxt->label, 1);
+
+	fdisk_info(cxt, _("Disk identifier changed from 0x%08x to 0x%08x."),
+			old, id);
+	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, ul_debug("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_label(cxt, DOS));
+
+	/* overwrite necessary stuff by DOS deprecated stuff */
+	if (is_dos_compatible(cxt)) {
+		DBG(LABEL, ul_debug("DOS: reseting alignemnt for DOS-comaptiblem PT"));
+		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_label(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_used_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 %zu"),
+					i + 1);
+			else
+				read_extended(cxt, i);
+		}
+	}
+
+	for (i = 3; i < cxt->label->nparts_max; i++) {
+		struct pte *pe = self_pte(cxt, i);
+		struct fdisk_dos_label *l = self_label(cxt);
+
+		if (!mbr_is_valid_magic(pe->sectorbuffer)) {
+			fdisk_info(cxt, _(
+			"Invalid flag 0x%02x%02x of EBR (for partition %zu) will "
+			"be corrected by w(rite)."),
+				pe->sectorbuffer[510],
+				pe->sectorbuffer[511],
+				i + 1);
+			partition_set_changed(cxt, i, 1);
+
+			/* mark also extended as changed to update the first EBR
+			 * in situation that there is no logical partitions at all */
+			partition_set_changed(cxt, l->ext_index, 1);
+		}
+	}
+
+	return 1;
+}
+
+static void set_partition(struct fdisk_context *cxt,
+			  int i, int doext, fdisk_sector_t start,
+			  fdisk_sector_t stop, int sysid, int boot)
+{
+	struct pte *pe = self_pte(cxt, i);
+	struct dos_partition *p;
+	fdisk_sector_t offset;
+
+	assert(!FDISK_IS_UNDEF(start));
+	assert(!FDISK_IS_UNDEF(stop));
+
+	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;
+	}
+
+	DBG(LABEL, ul_debug("DOS: setting partition %d%s, offset=%zu, start=%zu, stop=%zu, sysid=%02x",
+				i, doext ? " [extended]" : "",
+				(size_t) offset,
+				(size_t) (start -  offset),
+				(size_t) (stop - start + 1),
+				sysid));
+
+	p->boot_ind = boot ? ACTIVE_FLAG : 0;
+	p->sys_ind = sysid;
+	dos_partition_set_start(p, start - offset);
+	dos_partition_set_size(p, stop - start + 1);
+
+	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 fdisk_sector_t get_unused_start(struct fdisk_context *cxt,
+				 int part_n, fdisk_sector_t start,
+				 fdisk_sector_t first[], fdisk_sector_t last[])
+{
+	size_t i;
+
+	for (i = 0; i < cxt->label->nparts_max; i++) {
+		fdisk_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,
+			fdisk_sector_t *first, fdisk_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 (is_cleared_partition(p) || IS_EXTENDED (p->sys_ind)) {
+			first[i] = 0xffffffff;
+			last[i] = 0;
+		} else {
+			first[i] = get_abs_partition_start(pe);
+			last[i]  = get_abs_partition_end(pe);
+		}
+	}
+}
+
+static int get_start_from_user(	struct fdisk_context *cxt,
+				fdisk_sector_t *start,
+				fdisk_sector_t low,
+				fdisk_sector_t dflt,
+				fdisk_sector_t limit,
+				struct fdisk_partition *pa)
+{
+	assert(start);
+
+	/* try to use tepmlate from 'pa' */
+	if (pa && pa->start_follow_default)
+		*start = dflt;
+
+	else if (pa && fdisk_partition_has_start(pa)) {
+		DBG(LABEL, ul_debug("DOS: start: wanted=%ju, low=%ju, limit=%ju",
+				(uintmax_t) pa->start, (uintmax_t) low, (uintmax_t) limit));
+		*start = pa->start;
+		if (*start < low || *start > limit) {
+			fdisk_warnx(cxt, _("Start sector %ju out of range."),
+					(uintmax_t) *start);
+			return -ERANGE;
+		}
+	} else {
+		/* ask user by dialog */
+		struct fdisk_ask *ask = fdisk_new_ask();
+		int rc;
+
+		if (!ask)
+			return -ENOMEM;
+		fdisk_ask_set_query(ask,
+			fdisk_use_cylinders(cxt) ?
+				_("First cylinder") : _("First sector"));
+		fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+		fdisk_ask_number_set_low(ask, fdisk_cround(cxt, low));
+		fdisk_ask_number_set_default(ask, fdisk_cround(cxt, dflt));
+		fdisk_ask_number_set_high(ask, fdisk_cround(cxt, limit));
+
+		rc = fdisk_do_ask(cxt, ask);
+		*start = fdisk_ask_number_get_result(ask);
+		fdisk_unref_ask(ask);
+		if (rc)
+			return rc;
+		if (fdisk_use_cylinders(cxt)) {
+		        *start = (*start - 1)
+				* fdisk_get_units_per_sector(cxt);
+			if (*start < low)
+				*start = low;
+		}
+	}
+
+	DBG(LABEL, ul_debug("DOS: start is %ju", (uintmax_t) *start));
+	return 0;
+}
+
+static fdisk_sector_t get_possible_last(struct fdisk_context *cxt, size_t n)
+{
+	fdisk_sector_t limit;
+
+	if (n >= 4) {
+		/* logical partitions */
+		struct fdisk_dos_label *l = self_label(cxt);
+		struct pte *ext_pe = l->ext_offset ? self_pte(cxt, l->ext_index) : NULL;
+
+		if (!ext_pe)
+			return 0;
+		limit = get_abs_partition_end(ext_pe);
+	} else {
+		/* primary partitions */
+		if (fdisk_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;
+	}
+
+	DBG(LABEL, ul_debug("DOS: last possible sector for #%zu is %ju",
+				n, (uintmax_t) limit));
+	return limit;
+}
+
+/* returns last free sector for area addressed by @start, the first[] and
+ * last[] are fill_bounds() results */
+static fdisk_sector_t get_unused_last(struct fdisk_context *cxt, size_t n,
+				fdisk_sector_t start,
+				fdisk_sector_t first[], fdisk_sector_t last[])
+{
+	size_t i;
+	fdisk_sector_t limit = get_possible_last(cxt, n);
+
+	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;
+	}
+
+	DBG(LABEL, ul_debug("DOS: unused sector for #%zu is %ju",
+				n, (uintmax_t) limit));
+	return limit;
+}
+
+static int add_partition(struct fdisk_context *cxt, size_t n,
+			 struct fdisk_partition *pa)
+{
+	int sys, read = 0, rc, isrel = 0;
+	size_t i;
+	struct fdisk_dos_label *l = self_label(cxt);
+	struct dos_partition *p = self_partition(cxt, n);
+	struct pte *ext_pe = l->ext_offset ? self_pte(cxt, l->ext_index) : NULL;
+
+	fdisk_sector_t start, stop = 0, limit, temp,
+		first[cxt->label->nparts_max],
+		last[cxt->label->nparts_max];
+
+	DBG(LABEL, ul_debug("DOS: adding partition %zu", n));
+
+	sys = pa && pa->type ? pa->type->code : MBR_LINUX_DATA_PARTITION;
+
+	if (is_used_partition(p)) {
+		fdisk_warnx(cxt, _("Partition %zu is already defined.  "
+			           "Delete it before re-adding it."),
+				n + 1);
+		return -EINVAL;
+	}
+	fill_bounds(cxt, first, last);
+	limit = get_possible_last(cxt, n);
+
+	if (n < 4) {
+		if (cxt->parent && fdisk_is_label(cxt->parent, GPT))
+			start = 1;		/* Bad boy modifies hybrid MBR */
+		else {
+			if (cxt->script && pa && fdisk_partition_has_start(pa)
+			    && pa->start < cxt->first_lba
+			    && pa->start >= 1)
+				fdisk_set_first_lba(cxt, 1);
+
+			start = cxt->first_lba;
+		}
+
+		if (l->ext_offset) {
+			assert(ext_pe);
+			first[l->ext_index] = l->ext_offset;
+			last[l->ext_index] = get_abs_partition_end(ext_pe);
+		}
+	} else {
+		assert(ext_pe);
+
+		if (cxt->script && pa && fdisk_partition_has_start(pa)
+		    && pa->start >= l->ext_offset
+		    && pa->start < l->ext_offset + cxt->first_lba)
+			fdisk_set_first_lba(cxt, 1);
+
+		start = l->ext_offset + cxt->first_lba;
+	}
+
+	if (fdisk_use_cylinders(cxt))
+		for (i = 0; i < cxt->label->nparts_max; i++) {
+			first[i] = (fdisk_cround(cxt, first[i]) - 1)
+				* fdisk_get_units_per_sector(cxt);
+		}
+
+	/*
+	 * Ask for first sector
+	 */
+	do {
+		fdisk_sector_t dflt, aligned;
+
+		temp = start;
+		dflt = start = get_unused_start(cxt, n, start, first, last);
+
+		if (n >= 4 && pa && fdisk_partition_has_start(pa) && cxt->script
+		    && cxt->first_lba > 1
+		    && temp == start - cxt->first_lba) {
+			fdisk_set_first_lba(cxt, 1);
+			start = pa->start;
+		}
+
+		/* 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_get_units_per_sector(cxt)
+		    && read) {
+			fdisk_info(cxt, _("Sector %llu is already allocated."),
+					temp);
+			temp = start;
+			read = 0;
+			if (pa && (fdisk_partition_has_start(pa) ||
+				   pa->start_follow_default))
+				break;
+		}
+
+		if (!read && start == temp) {
+			rc = get_start_from_user(cxt, &start, temp, dflt, limit, pa);
+			if (rc)
+				return rc;
+			read = 1;
+		}
+	} while (start != temp || !read);
+
+	if (n == 4) {
+		/* The first EBR is stored at begin of the extended partition */
+		struct pte *pe = self_pte(cxt, n);
+		pe->offset = l->ext_offset;
+
+	} else if (n > 4) {
+		/* The second (and another) EBR */
+		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++;
+		}
+	}
+
+	limit = get_unused_last(cxt, n, start, first, last);
+
+	if (start > limit) {
+		fdisk_info(cxt, _("No free sectors available."));
+		if (n > 4)
+			cxt->label->nparts_max--;
+		return -ENOSPC;
+	}
+
+	/*
+	 * Ask for last sector
+	 */
+	if (fdisk_cround(cxt, start) == fdisk_cround(cxt, limit))
+		stop = limit;
+	else if (pa && pa->end_follow_default)
+		stop = limit;
+	else if (pa && fdisk_partition_has_size(pa)) {
+		stop = start + pa->size - 1;
+		isrel = pa->size_explicit ? 0 : 1;
+	} else {
+		/* ask user by dialog */
+		struct fdisk_ask *ask = fdisk_new_ask();
+
+		if (!ask)
+			return -ENOMEM;
+		fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET);
+
+		if (fdisk_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_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, fdisk_cround(cxt, start));
+		fdisk_ask_number_set_default(ask, fdisk_cround(cxt, limit));
+		fdisk_ask_number_set_high(ask, fdisk_cround(cxt, limit));
+		fdisk_ask_number_set_base(ask, fdisk_cround(cxt, start));	/* base for relative input */
+
+		rc = fdisk_do_ask(cxt, ask);
+		stop = fdisk_ask_number_get_result(ask);
+		isrel = fdisk_ask_number_is_relative(ask);
+		fdisk_unref_ask(ask);
+		if (rc)
+			return rc;
+		if (fdisk_use_cylinders(cxt)) {
+			stop = stop * fdisk_get_units_per_sector(cxt) - 1;
+			if (stop >limit)
+				stop = limit;
+		}
+	}
+
+	DBG(LABEL, ul_debug("DOS: raw stop: %ju", (uintmax_t) stop));
+
+	if (stop > limit)
+		stop = limit;
+
+	if (stop < limit) {
+		if (isrel && 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;
+		}
+	}
+
+	set_partition(cxt, n, 0, start, stop, sys, pa && pa->boot == 1 ? 1 : 0);
+	if (n > 4) {
+		struct pte *pe = self_pte(cxt, n);
+		set_partition(cxt, n - 1, 1, pe->offset, stop,
+					MBR_DOS_EXTENDED_PARTITION, 0);
+	}
+
+	/* report */
+	{
+		struct fdisk_parttype *t =
+			fdisk_label_get_parttype_from_code(cxt->label, sys);
+		fdisk_info_new_partition(cxt, n + 1, start, stop, t);
+		fdisk_unref_parttype(t);
+	}
+
+
+	if (IS_EXTENDED(sys)) {
+		struct pte *pen = self_pte(cxt, n);
+
+		l->ext_index = n;
+		l->ext_offset = start;
+		pen->ex_entry = p;
+	}
+
+	fdisk_label_set_changed(cxt->label, 1);
+	return 0;
+}
+
+static int add_logical(struct fdisk_context *cxt,
+		       struct fdisk_partition *pa,
+		       size_t *partno)
+{
+	struct pte *pe;
+	int rc;
+
+	assert(cxt);
+	assert(partno);
+	assert(cxt->label);
+	assert(self_label(cxt)->ext_offset);
+
+	DBG(LABEL, ul_debug("DOS: nparts max: %zu", cxt->label->nparts_max));
+	pe = self_pte(cxt, cxt->label->nparts_max);
+
+	if (!pe->sectorbuffer) {
+		pe->sectorbuffer = calloc(1, cxt->sector_size);
+		if (!pe->sectorbuffer)
+			return -ENOMEM;
+		DBG(LABEL, ul_debug("DOS: logical: %zu: new EBR sector buffer %p",
+					cxt->label->nparts_max, pe->sectorbuffer));
+		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++;
+
+	/* this message makes sense only when we use extended/primary/logical
+	 * dialog. The dialog is disable for scripts, see dos_add_partition() */
+	if (!cxt->script)
+		fdisk_info(cxt, _("Adding logical partition %zu"),
+				cxt->label->nparts_max);
+	*partno = cxt->label->nparts_max - 1;
+	rc = add_partition(cxt, *partno, pa);
+
+	if (rc) {
+		/* reset on error */
+		cxt->label->nparts_max--;
+		pe->pt_entry = NULL;
+		pe->ex_entry = NULL;
+		pe->offset = 0;
+		pe->changed = 0;
+	}
+
+	return rc;
+}
+
+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;
+
+	if (!is_dos_compatible(cxt))
+		return;
+
+	real_s = sector(s) - 1;
+	real_c = cylinder(s, c);
+	total = (real_c * cxt->geom.heads + h) * cxt->geom.sectors + real_s;
+
+	if (!total)
+		fdisk_warnx(cxt, _("Partition %zu: contains sector 0"), n);
+	if (h >= cxt->geom.heads)
+		fdisk_warnx(cxt, _("Partition %zu: head %d greater than "
+				   "maximum %d"), n, h + 1, cxt->geom.heads);
+	if (real_s >= cxt->geom.sectors)
+		fdisk_warnx(cxt, _("Partition %zu: sector %d greater than "
+				   "maximum %llu"), n, s, cxt->geom.sectors);
+	if (real_c >= cxt->geom.cylinders)
+		fdisk_warnx(cxt, _("Partition %zu: cylinder %d greater than "
+				   "maximum %llu"),
+				n, real_c + 1,
+				cxt->geom.cylinders);
+
+	if (cxt->geom.cylinders <= 1024 && start != total)
+		fdisk_warnx(cxt, _("Partition %zu: previous sectors %u "
+				   "disagrees with total %u"), 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 %zu: 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 %zu: 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 %zu: does not end on "
+				   "cylinder boundary."),
+			partition + 1);
+	}
+}
+
+static int dos_verify_disklabel(struct fdisk_context *cxt)
+{
+	size_t i, j;
+	fdisk_sector_t total = 1, n_sectors = cxt->total_sectors;
+	fdisk_sector_t 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_label(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 (is_used_partition(p) && !IS_EXTENDED(p->sys_ind)) {
+			check_consistency(cxt, p, i);
+			if (get_abs_partition_start(pe) < first[i])
+				fdisk_warnx(cxt, _(
+					"Partition %zu: bad start-of-data."),
+					 i + 1);
+
+			check(cxt, i + 1, p->eh, p->es, p->ec, last[i]);
+			total += last[i] + 1 - first[i];
+
+			if (i == 0)
+				total += get_abs_partition_start(pe) - 1;
+
+			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 %zu: "
+						"overlaps partition %zu."),
+						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) {
+		fdisk_sector_t e_last;
+		struct pte *ext_pe = self_pte(cxt, l->ext_index);
+
+		e_last = get_abs_partition_end(ext_pe);
+
+		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 %zu: empty."),
+						i + 1);
+			} else if (first[i] < l->ext_offset
+				   || last[i] > e_last) {
+
+				fdisk_warnx(cxt, _("Logical partition %zu: "
+					"not entirely in partition %zu."),
+					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,
+			     struct fdisk_partition *pa,
+			     size_t *partno)
+{
+	size_t i, free_primary = 0, free_sectors = 0;
+	fdisk_sector_t last = 0, grain;
+	int rc = 0;
+	struct fdisk_dos_label *l;
+	struct pte *ext_pe;
+	size_t res;		/* partno */
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	DBG(LABEL, ul_debug("DOS: new partition wanted"));
+
+	l = self_label(cxt);
+	ext_pe = l->ext_offset ? self_pte(cxt, l->ext_index) : NULL;
+
+	/*
+	 * partition template (@pa) based partitioning
+	 */
+
+	/* pa specifies start within extended partition, add logical */
+	if (pa && fdisk_partition_has_start(pa) && ext_pe
+	    && pa->start >= l->ext_offset
+	    && pa->start <= get_abs_partition_end(ext_pe)) {
+		DBG(LABEL, ul_debug("DOS: pa template %p: add logical", pa));
+		rc = add_logical(cxt, pa, &res);
+		goto done;
+
+	/* pa specifies that extended partition is wanted */
+	} else if (pa && pa->type && pa->type->code == MBR_DOS_EXTENDED_PARTITION) {
+		DBG(LABEL, ul_debug("DOS: pa template %p: add extened", pa));
+		if (l->ext_offset) {
+			fdisk_warnx(cxt, _("Extended partition already exists."));
+			return -EINVAL;
+		}
+		rc = get_partition_unused_primary(cxt, pa, &res);
+		if (rc == 0) {
+			rc = add_partition(cxt, res, pa);
+			goto done;
+		}
+
+	/* pa specifies start, but outside extended partition */
+	} else if (pa && fdisk_partition_has_start(pa) && l->ext_offset) {
+		DBG(LABEL, ul_debug("DOS: pa template %p: add primary", pa));
+		rc = get_partition_unused_primary(cxt, pa, &res);
+		if (rc == 0) {
+			rc = add_partition(cxt, res, pa);
+			goto done;
+		}
+	}
+
+	/*
+	 * dialog driven partitioning (it does not mean that @pa template is
+	 * completely ignored!)
+	 */
+
+	/* check if there is space for primary partition */
+	grain = cxt->grain > cxt->sector_size ? cxt->grain / cxt->sector_size : 1;
+	last = cxt->first_lba;
+
+	for (i = 0; i < 4; i++) {
+		struct dos_partition *p = self_partition(cxt, i);
+
+		if (is_used_partition(p)) {
+			fdisk_sector_t start = dos_partition_get_start(p);
+			if (last + grain <= start)
+				free_sectors = 1;
+			last = start + dos_partition_get_size(p);
+		} else
+			free_primary++;
+	}
+	if (last + grain < cxt->total_sectors - 1)
+		free_sectors = 1;
+
+	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 || !free_sectors) {
+		DBG(LABEL, ul_debug("DOS: primary impossible, add logical"));
+		if (l->ext_offset) {
+			if (!pa || fdisk_partition_has_start(pa)) {
+				if (!free_primary)
+					fdisk_info(cxt, _("All primary partitions are in use."));
+				else if (!free_sectors)
+					fdisk_info(cxt, _("All space for primary partitions is in use."));
+			}
+			rc = add_logical(cxt, pa, &res);
+		} else {
+			fdisk_info(cxt,
+			     _(	"Impossible to create another primary partition. "
+				"If you want to create more partitions, you must "
+				"replace a primary partition with an extended "
+				"partition first."));
+			return -EINVAL;
+		}
+	} else if (cxt->label->nparts_max >= MAXIMUM_PARTS) {
+		fdisk_info(cxt, _("All logical partitions are in use. "
+				  "Adding a primary partition."));
+		rc = get_partition_unused_primary(cxt, pa, &res);
+		if (rc == 0)
+			rc = add_partition(cxt, res, pa);
+	} else {
+		char hint[BUFSIZ];
+		struct fdisk_ask *ask;
+		int c;
+
+		/* the default layout for scripts is to create primary partitions */
+		if (cxt->script) {
+			rc = get_partition_unused_primary(cxt, pa, &res);
+			if (rc == 0)
+				rc = add_partition(cxt, res, pa);
+			goto done;
+		}
+
+		ask = fdisk_new_ask();
+		if (!ask)
+			return -ENOMEM;
+		fdisk_ask_set_type(ask, FDISK_ASKTYPE_MENU);
+		fdisk_ask_set_query(ask, _("Partition type"));
+		fdisk_ask_menu_set_default(ask, free_primary == 1
+						&& !l->ext_offset ? 'e' : 'p');
+		snprintf(hint, sizeof(hint),
+				_("%zu primary, %d extended, %zu free"),
+				4 - (l->ext_offset ? 1 : 0) - free_primary,
+				l->ext_offset ? 1 : 0,
+				free_primary);
+
+		fdisk_ask_menu_add_item(ask, 'p', _("primary"), hint);
+		if (!l->ext_offset)
+			fdisk_ask_menu_add_item(ask, 'e', _("extended"), _("container for logical partitions"));
+		else
+			fdisk_ask_menu_add_item(ask, 'l', _("logical"), _("numbered from 5"));
+
+		rc = fdisk_do_ask(cxt, ask);
+		if (rc)
+			return rc;
+		fdisk_ask_menu_get_result(ask, &c);
+		fdisk_unref_ask(ask);
+
+		if (c == 'p') {
+			rc = get_partition_unused_primary(cxt, pa, &res);
+			if (rc == 0)
+				rc = add_partition(cxt, res, pa);
+			goto done;
+		} else if (c == 'l' && l->ext_offset) {
+			rc = add_logical(cxt, pa, &res);
+			goto done;
+		} else if (c == 'e' && !l->ext_offset) {
+			rc = get_partition_unused_primary(cxt, pa, &res);
+			if (rc == 0) {
+				struct fdisk_partition *xpa = NULL;
+				struct fdisk_parttype *t;
+
+				t = fdisk_label_get_parttype_from_code(cxt->label,
+						MBR_DOS_EXTENDED_PARTITION);
+				if (!pa) {
+					pa = xpa = fdisk_new_partition();
+					if (!xpa)
+						return -ENOMEM;
+				}
+				fdisk_partition_set_type(pa, t);
+				rc = add_partition(cxt, res, pa);
+				if (xpa) {
+					fdisk_unref_partition(xpa);
+					pa = NULL;
+				}
+			}
+			goto done;
+		} else
+			fdisk_warnx(cxt, _("Invalid partition type `%c'."), c);
+	}
+done:
+	if (rc == 0) {
+		cxt->label->nparts_cur++;
+		if (partno)
+			*partno = res;
+	}
+	return rc;
+}
+
+static int write_sector(struct fdisk_context *cxt, fdisk_sector_t secno,
+			       unsigned char *buf)
+{
+	int rc;
+
+	rc = seek_sector(cxt, secno);
+	if (rc != 0) {
+		fdisk_warn(cxt, _("Cannot write sector %jd: seek failed"),
+				(uintmax_t) secno);
+		return rc;
+	}
+
+	DBG(LABEL, ul_debug("DOS: writting to sector %ju", (uintmax_t) secno));
+
+	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_label(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;
+	}
+
+	if (cxt->label->nparts_max <= 4 && l->ext_offset) {
+		/* we have empty extended partition, check if the partition has
+		 * been modified and then cleanup possible remaining EBR  */
+		struct pte *pe = self_pte(cxt, l->ext_index);
+		unsigned char empty[512] = { 0 };
+		fdisk_sector_t off = pe ? get_abs_partition_start(pe) : 0;
+
+		if (off && pe->changed) {
+			mbr_set_magic(empty);
+			write_sector(cxt, off, empty);
+		}
+	}
+
+	/* EBR (logical partitions) */
+	for (i = 4; i < cxt->label->nparts_max; i++) {
+		struct pte *pe = self_pte(cxt, i);
+
+		if (!pe->changed || !pe->offset || !pe->sectorbuffer)
+			continue;
+
+		mbr_set_magic(pe->sectorbuffer);
+		rc = write_sector(cxt, pe->offset, pe->sectorbuffer);
+		if (rc)
+			goto done;
+	}
+
+done:
+	return rc;
+}
+
+static int dos_locate_disklabel(struct fdisk_context *cxt, int n,
+		const char **name, off_t *offset, size_t *size)
+{
+	assert(cxt);
+
+	*name = NULL;
+	*offset = 0;
+	*size = 0;
+
+	switch (n) {
+	case 0:
+		*name = "MBR";
+		*offset = 0;
+		*size = 512;
+		break;
+	default:
+		/* extended partitions */
+		if (n - 1 + 4 < cxt->label->nparts_max) {
+			struct pte *pe = self_pte(cxt, n - 1 + 4);
+
+			assert(pe->private_sectorbuffer);
+
+			*name = "EBR";
+			*offset = pe->offset * cxt->sector_size;
+			*size = 512;
+		} else
+			return 1;
+		break;
+	}
+
+	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 (is_used_partition(p)) {
+			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 dos_list_disklabel(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	return 0;
+}
+
+static int dos_get_partition(struct fdisk_context *cxt, size_t n,
+			     struct fdisk_partition *pa)
+{
+	struct dos_partition *p;
+	struct pte *pe;
+	struct fdisk_dos_label *lb;
+
+	assert(cxt);
+	assert(pa);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	lb = self_label(cxt);
+	pe = self_pte(cxt, n);
+	p = pe->pt_entry;
+	pa->used = !is_cleared_partition(p);
+	if (!pa->used)
+		return 0;
+
+	pa->type = dos_partition_parttype(cxt, p);
+	pa->boot = p->boot_ind == ACTIVE_FLAG ? 1 : 0;
+	pa->start = get_abs_partition_start(pe);
+	pa->size = dos_partition_get_size(p);
+	pa->container = lb->ext_offset && n == lb->ext_index;
+
+	if (n >= 4)
+		pa->parent_partno = lb->ext_index;
+
+	if (p->boot_ind && asprintf(&pa->attrs, "%02x", p->boot_ind) < 0)
+		return -ENOMEM;
+
+	/* start C/H/S */
+	if (asprintf(&pa->start_chs, "%d/%d/%d",
+				cylinder(p->bs, p->bc),
+				sector(p->bs),
+				p->bh) < 0)
+		return -ENOMEM;
+
+	/* end C/H/S */
+	if (asprintf(&pa->end_chs, "%d/%d/%d",
+				cylinder(p->es, p->ec),
+				sector(p->es),
+				p->eh) < 0)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static int dos_set_partition(struct fdisk_context *cxt, size_t n,
+			     struct fdisk_partition *pa)
+{
+	struct fdisk_dos_label *l;
+	struct dos_partition *p;
+	struct pte *pe;
+	fdisk_sector_t start, size;
+
+	assert(cxt);
+	assert(pa);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	if (n >= cxt->label->nparts_max)
+		return -EINVAL;
+
+	if (pa->type && IS_EXTENDED(pa->type->code)) {
+		fdisk_warnx(cxt, _("You cannot change a partition into an "
+			"extended one or vice versa. Delete it first."));
+		return -EINVAL;
+	}
+
+	if (pa->type && !pa->type->code)
+		fdisk_warnx(cxt, _("Type 0 means free space to many systems. "
+				   "Having partitions of type 0 is probably unwise."));
+	l = self_label(cxt);
+	p = self_partition(cxt, n);
+	pe = self_pte(cxt, n);
+
+	FDISK_INIT_UNDEF(start);
+	FDISK_INIT_UNDEF(size);
+
+	if (fdisk_partition_has_start(pa))
+		start = pa->start;
+	if (fdisk_partition_has_size(pa))
+		size = pa->size;
+
+	if (pa->end_follow_default) {
+		fdisk_sector_t first[cxt->label->nparts_max],
+			 last[cxt->label->nparts_max],
+			 xlast;
+		struct pte *ext = l->ext_offset ? self_pte(cxt, l->ext_index) : NULL; 
+
+		fill_bounds(cxt, first, last);
+
+		if (ext && l->ext_offset) {
+			first[l->ext_index] = l->ext_offset;
+			last[l->ext_index] = get_abs_partition_end(ext);
+		}
+		if (FDISK_IS_UNDEF(start))
+			start = get_abs_partition_start(pe);
+
+		DBG(LABEL, ul_debug("DOS: #%zu    now %ju +%ju sectors",
+			n, (uintmax_t) start, (uintmax_t) dos_partition_get_size(p)));
+
+		xlast = get_unused_last(cxt, n, start, first, last);
+		size = xlast ? xlast - start + 1: dos_partition_get_size(p);
+
+		DBG(LABEL, ul_debug("DOS: #%zu wanted %ju +%ju sectors",
+			n, (uintmax_t) start, (uintmax_t) size));
+	}
+
+	if (!FDISK_IS_UNDEF(start) || !FDISK_IS_UNDEF(size)) {
+		DBG(LABEL, ul_debug("DOS: resize partition"));
+
+		if (FDISK_IS_UNDEF(start))
+			start = get_abs_partition_start(pe);
+		if (FDISK_IS_UNDEF(size))
+			size = dos_partition_get_size(p);
+
+		set_partition(cxt, n, 0, start, start + size - 1,
+				pa->type  ? pa->type->code : p->sys_ind,
+				pa->boot == 1);
+	} else {
+		DBG(LABEL, ul_debug("DOS: keep size, modify properties"));
+		if (pa->type)
+			p->sys_ind = pa->type->code;
+		if (!FDISK_IS_UNDEF(pa->boot))
+			p->boot_ind = pa->boot == 1 ? ACTIVE_FLAG : 0;
+	}
+
+	partition_set_changed(cxt, n, 1);
+	return 0;
+}
+
+static void print_chain_of_logicals(struct fdisk_context *cxt)
+{
+	size_t i;
+	struct fdisk_dos_label *l = self_label(cxt);
+
+	fputc('\n', stdout);
+
+	for (i = 4; i < cxt->label->nparts_max; i++) {
+		struct pte *pe = self_pte(cxt, i);
+
+		fprintf(stderr, "#%02zu EBR [%10ju], "
+			"data[start=%10ju (%10ju), size=%10ju], "
+			"link[start=%10ju (%10ju), size=%10ju]\n",
+			i, (uintmax_t) pe->offset,
+			/* data */
+			(uintmax_t) dos_partition_get_start(pe->pt_entry),
+			(uintmax_t) get_abs_partition_start(pe),
+			(uintmax_t) dos_partition_get_size(pe->pt_entry),
+			/* link */
+			(uintmax_t) dos_partition_get_start(pe->ex_entry),
+			(uintmax_t) l->ext_offset + dos_partition_get_start(pe->ex_entry),
+			(uintmax_t) dos_partition_get_size(pe->ex_entry));
+	}
+}
+
+static int cmp_ebr_offsets(const void *a, const void *b)
+{
+	struct pte *ae = (struct pte *) a,
+		   *be = (struct pte *) b;
+
+	if (ae->offset == 0 && be->offset == 0)
+		return 0;
+	if (ae->offset == 0)
+		return 1;
+	if (be->offset == 0)
+		return -1;
+
+	return cmp_numbers(ae->offset, be->offset);
+}
+
+/*
+ * Fix the chain of logicals.
+ *
+ * The function does not modify data partitions within EBR tables
+ * (pte->pt_entry). It sorts the chain by EBR offsets and then update links
+ * (pte->ex_entry) between EBR tables.
+ *
+ */
+static void fix_chain_of_logicals(struct fdisk_context *cxt)
+{
+	struct fdisk_dos_label *l = self_label(cxt);
+	struct pte *last;
+	size_t i;
+
+	DBG(LABEL, print_chain_of_logicals(cxt));
+
+	/* Sort chain by EBR offsets */
+	qsort(&l->ptes[4], cxt->label->nparts_max - 4, sizeof(struct pte),
+			cmp_ebr_offsets);
+
+again:
+	/* Sort data partitions by start */
+	for (i = 4; i < cxt->label->nparts_max - 1; i++) {
+		struct pte *cur = self_pte(cxt, i),
+			   *nxt = self_pte(cxt, i + 1);
+
+		if (get_abs_partition_start(cur) >
+		    get_abs_partition_start(nxt)) {
+
+			struct dos_partition tmp = *cur->pt_entry;
+			fdisk_sector_t cur_start = get_abs_partition_start(cur),
+				 nxt_start = get_abs_partition_start(nxt);
+
+			/* swap data partitions */
+			*cur->pt_entry = *nxt->pt_entry;
+			*nxt->pt_entry = tmp;
+
+			/* Recount starts according to EBR offsets, the absolute
+			 * address tas to be still the same! */
+			dos_partition_set_start(cur->pt_entry, nxt_start - cur->offset);
+			dos_partition_set_start(nxt->pt_entry, cur_start - nxt->offset);
+
+			partition_set_changed(cxt, i, 1);
+			partition_set_changed(cxt, i + 1, 1);
+			goto again;
+		}
+	}
+
+	/* Update EBR links */
+	for (i = 4; i < cxt->label->nparts_max - 1; i++) {
+		struct pte *cur = self_pte(cxt, i),
+			   *nxt = self_pte(cxt, i + 1);
+
+		fdisk_sector_t noff = nxt->offset - l->ext_offset,
+			 ooff = dos_partition_get_start(cur->ex_entry);
+
+		if (noff == ooff)
+			continue;
+
+		DBG(LABEL, ul_debug("DOS: fix EBR [%10ju] link %ju -> %ju",
+			(uintmax_t) cur->offset,
+			(uintmax_t) ooff, (uintmax_t) noff));
+
+		set_partition(cxt, i, 1, nxt->offset,
+				get_abs_partition_end(nxt),
+				MBR_DOS_EXTENDED_PARTITION, 0);
+	}
+
+	/* always terminate the chain ! */
+	last = self_pte(cxt, cxt->label->nparts_max - 1);
+	if (last) {
+		clear_partition(last->ex_entry);
+		partition_set_changed(cxt, cxt->label->nparts_max - 1, 1);
+	}
+
+	DBG(LABEL, print_chain_of_logicals(cxt));
+}
+
+static int dos_reorder(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;
+}
+
+/* TODO: use fdisk_set_partition() API */
+int fdisk_dos_move_begin(struct fdisk_context *cxt, size_t 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_label(cxt, DOS));
+
+	pe = self_pte(cxt, i);
+	p = pe->pt_entry;
+
+	if (!is_used_partition(p) || IS_EXTENDED (p->sys_ind)) {
+		fdisk_warnx(cxt, _("Partition %zu: 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_used_partition(prev_p) &&
+		    end > free_start && end <= curr_start)
+			free_start = end;
+	}
+
+	last = get_abs_partition_end(pe);
+
+	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_partition_is_used(
+		struct fdisk_context *cxt,
+		size_t i)
+{
+	struct dos_partition *p;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	if (i >= cxt->label->nparts_max)
+		return 0;
+
+	p = self_partition(cxt, i);
+
+	return p && !is_cleared_partition(p);
+}
+
+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_label(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 %zu: is an extended "
+					"partition."), i + 1);
+
+		p->boot_ind = (p->boot_ind ? 0 : ACTIVE_FLAG);
+		partition_set_changed(cxt, i, 1);
+		fdisk_info(cxt,	p->boot_ind ?
+			_("The bootable flag on partition %zu is enabled now.") :
+			_("The bootable flag on partition %zu is disabled now."),
+			i + 1);
+		break;
+	default:
+		return 1;
+	}
+
+	return 0;
+}
+
+static const struct fdisk_field dos_fields[] =
+{
+	/* basic */
+	{ FDISK_FIELD_DEVICE,	N_("Device"),	 10,	0 },
+	{ FDISK_FIELD_BOOT,	N_("Boot"),	  1,	0 },
+	{ FDISK_FIELD_START,	N_("Start"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_END,	N_("End"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_SECTORS,	N_("Sectors"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_CYLINDERS,N_("Cylinders"),  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_SIZE,	N_("Size"),	  5,	FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_EYECANDY },
+	{ FDISK_FIELD_TYPEID,	N_("Id"),	  2,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_TYPE,	N_("Type"),	0.1,	0 },
+
+	/* expert mode */
+	{ FDISK_FIELD_SADDR,	N_("Start-C/H/S"), 1,   FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_DETAIL },
+	{ FDISK_FIELD_EADDR,	N_("End-C/H/S"),   1,   FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_DETAIL },
+	{ FDISK_FIELD_ATTR,	N_("Attrs"),	   2,   FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_DETAIL }
+
+};
+
+static const struct fdisk_label_operations dos_operations =
+{
+	.probe		= dos_probe_label,
+	.write		= dos_write_disklabel,
+	.verify		= dos_verify_disklabel,
+	.create		= dos_create_disklabel,
+	.locate		= dos_locate_disklabel,
+	.list		= dos_list_disklabel,
+	.reorder	= dos_reorder,
+	.get_id		= dos_get_disklabel_id,
+	.set_id		= dos_set_disklabel_id,
+
+	.get_part	= dos_get_partition,
+	.set_part	= dos_set_partition,
+	.add_part	= dos_add_partition,
+	.del_part	= dos_delete_partition,
+
+	.part_toggle_flag = dos_toggle_partition_flag,
+	.part_is_used	= dos_partition_is_used,
+
+	.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) - 1;
+	lb->fields = dos_fields;
+	lb->nfields = ARRAY_SIZE(dos_fields);
+
+	return lb;
+}
+
+/**
+ * fdisk_dos_enable_compatible:
+ * @lb: DOS label (see fdisk_get_label())
+ * @enable: 0 or 1
+ *
+ * Enables deprecated DOS compatible mode, in this mode library checks for
+ * cylinders boundary, cases about CHS addressing and another obscure things.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+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;
+}
+
+/**
+ * fdisk_dos_is_compatible:
+ * @lb: DOS label
+ *
+ * Returns: 0 if DOS compatibility disabled, 1 if enabled
+ */
+int fdisk_dos_is_compatible(struct fdisk_label *lb)
+{
+	return ((struct fdisk_dos_label *) lb)->compatible;
+}