blob: 2a067076e394788565fd6b1e83625224f8918baa [file] [log] [blame]
/*
*
* 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;
}