summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKarel Zak <kzak@redhat.com>2014-04-18 11:48:53 +0200
committerKarel Zak <kzak@redhat.com>2014-04-23 11:55:09 +0200
commit3a6ac12afdfd8d56771f2da49a4cf5fbc054403c (patch)
tree92b65e724df43a636913994a8d543943ccc06ea5
parentc79ce9df352af0911d6e4ab19f76c98aa7af9f0c (diff)
downloadutil-linux-3a6ac12afdfd8d56771f2da49a4cf5fbc054403c.tar.gz
libfdisk: fix logical partition reorder command
Reported-by: Olaf Hering <olaf@aepfle.de> Signed-off-by: Karel Zak <kzak@redhat.com>
-rw-r--r--libfdisk/src/dos.c159
1 files changed, 109 insertions, 50 deletions
diff --git a/libfdisk/src/dos.c b/libfdisk/src/dos.c
index 18ea8da95..1f5d8b33f 100644
--- a/libfdisk/src/dos.c
+++ b/libfdisk/src/dos.c
@@ -148,6 +148,17 @@ static sector_t get_abs_partition_start(struct pte *pe)
return pe->offset + dos_partition_get_start(pe->pt_entry);
}
+static sector_t get_abs_partition_end(struct pte *pe)
+{
+ 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);
+}
+
/*
* Linux kernel cares about partition size only. Things like
* partition type or so are completely irrelevant -- kzak Nov-2013
@@ -1723,69 +1734,117 @@ static int dos_list_disklabel(struct fdisk_context *cxt)
return rc;
}
+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);
+
+ printf("#%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 && ae->offset == 0)
+ return 0;
+ if (ae->offset == 0)
+ return 1;
+ if (be->offset == 0)
+ return -1;
+ return ae->offset - be->offset;
+}
/*
* 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.
+ * 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);
- 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;
+ 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;
+ 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;
}
}
- /* 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;
+ /* 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);
+
+ sector_t noff = nxt->offset - l->ext_offset,
+ ooff = dos_partition_get_start(cur->ex_entry);
+
+ if (noff == ooff)
+ continue;
+
+ DBG(LABEL, dbgprint("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);
+
+ if (i + 1 == cxt->label->nparts_max - 1) {
+ clear_partition(nxt->ex_entry);
+ partition_set_changed(cxt, i + 1, 1);
}
- }
- /* Probably something was changed */
- for (j = 4; j < cxt->label->nparts_max; j++)
- l->ptes[j].changed = 1;
+ }
+ DBG(LABEL, print_chain_of_logicals(cxt));
}
int fdisk_dos_fix_order(struct fdisk_context *cxt)