/* * Many, many hands. * Specific DOS label file - Davidlohr Bueso */ #include #include #include "nls.h" #include "xalloc.h" #include "randutils.h" #include "common.h" #include "fdisk.h" #include "fdiskdoslabel.h" /* * 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 */ char changed; /* boolean */ sector_t offset; /* disk sector number */ unsigned char *sectorbuffer; /* disk sector contents */ }; /* * in-memory fdisk GPT stuff */ struct fdisk_dos_label { struct fdisk_label head; /* generic part */ struct pte ptes[MAXIMUM_PARTS]; /* partition */ sector_t ext_offset; size_t ext_index; unsigned int compatible : 1, /* is DOS compatible? */ non_pt_changed : 1; /* MBR, but no PT changed */ }; /* * Partition types */ static struct fdisk_parttype dos_parttypes[] = { #include "dos_part_types.h" }; #define set_hsc(h,s,c,sector) { \ s = sector % cxt->geom.sectors + 1; \ sector /= cxt->geom.sectors; \ h = sector % cxt->geom.heads; \ sector /= cxt->geom.heads; \ c = sector & 0xff; \ s |= (sector >> 2) & 0xc0; \ } #define sector(s) ((s) & 0x3f) #define cylinder(s, c) ((c) | (((s) & 0xc0) << 2)) #define alignment_required(_x) ((_x)->grain != (_x)->sector_size) #define is_dos_compatible(_x) \ (fdisk_is_disklabel(_x, DOS) && \ fdisk_dos_is_compatible(fdisk_context_get_label(_x, NULL))) #define cround(c, n) (fdisk_context_use_cylinders(c) ? \ ((n) / fdisk_context_get_units_per_sector(c)) + 1 : (n)) static inline struct fdisk_dos_label *self_label(struct fdisk_context *cxt) { assert(cxt); assert(cxt->label); assert(fdisk_is_disklabel(cxt, DOS)); return (struct fdisk_dos_label *) cxt->label; } static inline struct pte *self_pte(struct fdisk_context *cxt, size_t i) { struct fdisk_dos_label *l = self_label(cxt); if (i >= ARRAY_SIZE(l->ptes)) return NULL; return &l->ptes[i]; } static inline struct dos_partition *self_partition( struct fdisk_context *cxt, size_t i) { struct pte *pe = self_pte(cxt, i); return pe ? pe->pt_entry : NULL; } struct dos_partition *fdisk_dos_get_partition( struct fdisk_context *cxt, size_t i) { return self_partition(cxt, i); } static void partition_set_changed( struct fdisk_context *cxt, size_t i, int changed) { struct pte *pe = self_pte(cxt, i); if (!pe) return; pe->changed = changed; if (changed) fdisk_label_set_changed(cxt->label, 1); } static sector_t get_abs_partition_start(struct pte *pe) { return pe->offset + dos_partition_get_start(pe->pt_entry); } static int is_cleared_partition(struct dos_partition *p) { return !(!p || p->boot_ind || p->bh || p->bs || p->bc || p->sys_ind || p->eh || p->es || p->ec || dos_partition_get_start(p) || dos_partition_get_size(p)); } static void warn_alignment(struct fdisk_context *cxt) { if (nowarn) return; 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 (is_dos_compatible(cxt)) fdisk_warnx(cxt, _("DOS-compatible mode is deprecated.")); if (fdisk_context_use_cylinders(cxt)) fdisk_warnx(cxt, _("Cylinders as display units are deprecated.")); } static int get_partition_unused_primary(struct fdisk_context *cxt) { size_t orgmax = cxt->label->nparts_max; size_t n; int rc; cxt->label->nparts_max = 4; rc = fdisk_ask_partnum(cxt, &n, TRUE); cxt->label->nparts_max = orgmax; switch (rc) { case 1: fdisk_info(cxt, _("All primary partitions have been defined already")); return -1; case 0: return n; default: return rc; } } /* Allocate a buffer and read a partition table sector */ static int read_pte(struct fdisk_context *cxt, int pno, sector_t offset) { unsigned char *buf; struct pte *pe = self_pte(cxt, pno); buf = calloc(1, cxt->sector_size); if (!buf) return -ENOMEM; pe->offset = offset; pe->sectorbuffer = buf; if (read_sector(cxt, offset, pe->sectorbuffer) != 0) fdisk_warn(cxt, _("Failed to read extended partition table " "(offset=%jd)"), (uintmax_t) offset); pe->changed = 0; pe->pt_entry = pe->ex_entry = NULL; return 0; } static void clear_partition(struct dos_partition *p) { if (!p) return; p->boot_ind = 0; p->bh = 0; p->bs = 0; p->bc = 0; p->sys_ind = 0; p->eh = 0; p->es = 0; p->ec = 0; dos_partition_set_start(p,0); dos_partition_set_size(p,0); } void dos_init(struct fdisk_context *cxt) { struct fdisk_dos_label *l = self_label(cxt); size_t i; cxt->label->nparts_max = 4; /* default, unlimited number of logical */ l->ext_index = 0; l->ext_offset = 0; for (i = 0; i < 4; i++) { struct pte *pe = self_pte(cxt, i); pe->pt_entry = mbr_get_partition(cxt->firstsector, i); pe->ex_entry = NULL; pe->offset = 0; pe->sectorbuffer = cxt->firstsector; pe->changed = 0; } warn_geometry(cxt); warn_alignment(cxt); if (cxt->total_sectors > UINT_MAX && !nowarn) { unsigned long long bytes = cxt->total_sectors * cxt->sector_size; int giga = bytes / 1000000000; int hectogiga = (giga + 50) / 100; fdisk_warnx(cxt, _("The size of this disk is %d.%d TB (%llu bytes). DOS " "partition table format can not be used on drives for " "volumes larger than (%llu bytes) for %ld-byte " "sectors. Use GUID partition table format (GPT)."), hectogiga / 10, hectogiga % 10, bytes, (sector_t ) UINT_MAX * cxt->sector_size, cxt->sector_size); } } static int dos_delete_partition(struct fdisk_context *cxt, size_t partnum) { struct fdisk_dos_label *l = self_label(cxt); struct pte *pe = self_pte(cxt, partnum); struct dos_partition *p; struct dos_partition *q; if (!pe) return -EINVAL; p = pe->pt_entry; q = pe->ex_entry; /* Note that for the fifth partition (partnum == 4) we don't actually decrement partitions. */ if (partnum < 4) { if (IS_EXTENDED(p->sys_ind) && partnum == l->ext_index) { cxt->label->nparts_max = 4; l->ptes[l->ext_index].ex_entry = NULL; l->ext_offset = 0; } partition_set_changed(cxt, partnum, 1); clear_partition(p); } else if (!q->sys_ind && partnum > 4) { /* the last one in the chain - just delete */ --cxt->label->nparts_max; --partnum; clear_partition(l->ptes[partnum].ex_entry); partition_set_changed(cxt, partnum, 1); } else { /* not the last one - further ones will be moved down */ if (partnum > 4) { /* delete this link in the chain */ p = l->ptes[partnum - 1].ex_entry; *p = *q; dos_partition_set_start(p, dos_partition_get_start(q)); dos_partition_set_size(p, dos_partition_get_size(q)); partition_set_changed(cxt, partnum - 1, 1); } else if (cxt->label->nparts_max > 5) { /* 5 will be moved to 4 */ /* the first logical in a longer chain */ pe = &l->ptes[5]; if (pe->pt_entry) /* prevent SEGFAULT */ dos_partition_set_start(pe->pt_entry, get_abs_partition_start(pe) - l->ext_offset); pe->offset = l->ext_offset; partition_set_changed(cxt, 5, 1); } if (cxt->label->nparts_max > 5) { cxt->label->nparts_max--; while (partnum < cxt->label->nparts_max) { l->ptes[partnum] = l->ptes[partnum + 1]; partnum++; } } else /* the only logical: clear only */ clear_partition(l->ptes[partnum].pt_entry); } fdisk_label_set_changed(cxt->label, 1); return 0; } static void read_extended(struct fdisk_context *cxt, int ext) { size_t i; struct pte *pex; struct dos_partition *p, *q; struct fdisk_dos_label *l = self_label(cxt); l->ext_index = ext; pex = self_pte(cxt, ext); pex->ex_entry = pex->pt_entry; p = pex->pt_entry; if (!dos_partition_get_start(p)) { fdisk_warnx(cxt, _("Bad offset in primary extended partition")); return; } while (IS_EXTENDED (p->sys_ind)) { struct pte *pe = self_pte(cxt, cxt->label->nparts_max); if (cxt->label->nparts_max >= MAXIMUM_PARTS) { /* This is not a Linux restriction, but this program uses arrays of size MAXIMUM_PARTS. Do not try to `improve' this test. */ struct pte *pre = self_pte(cxt, cxt->label->nparts_max - 1); fdisk_warnx(cxt, _("Omitting partitions after #%zd. They will be deleted " "if you save this partition table."), cxt->label->nparts_max); clear_partition(pre->ex_entry); partition_set_changed(cxt, cxt->label->nparts_max - 1, 1); return; } read_pte(cxt, cxt->label->nparts_max, l->ext_offset + dos_partition_get_start(p)); if (!l->ext_offset) l->ext_offset = dos_partition_get_start(p); q = p = mbr_get_partition(pe->sectorbuffer, 0); for (i = 0; i < 4; i++, p++) if (dos_partition_get_size(p)) { if (IS_EXTENDED (p->sys_ind)) { if (pe->ex_entry) fdisk_warnx(cxt, _( "Extra link pointer in partition " "table %zd"), cxt->label->nparts_max + 1); else pe->ex_entry = p; } else if (p->sys_ind) { if (pe->pt_entry) fdisk_warnx(cxt, _( "Ignoring extra data in partition " "table %zd"), cxt->label->nparts_max + 1); else pe->pt_entry = p; } } /* very strange code here... */ if (!pe->pt_entry) { if (q != pe->ex_entry) pe->pt_entry = q; else pe->pt_entry = q + 1; } if (!pe->ex_entry) { if (q != pe->pt_entry) pe->ex_entry = q; else pe->ex_entry = q + 1; } p = pe->ex_entry; cxt->label->nparts_cur = ++cxt->label->nparts_max; } /* remove empty links */ remove: q = self_partition(cxt, 4); for (i = 4; i < cxt->label->nparts_max; i++) { p = self_partition(cxt, i); if (!dos_partition_get_size(p) && (cxt->label->nparts_max > 5 || q->sys_ind)) { fdisk_info(cxt, _("omitting empty partition (%zd)"), i+1); dos_delete_partition(cxt, i); goto remove; /* numbering changed */ } } } void dos_print_mbr_id(struct fdisk_context *cxt) { fdisk_info(cxt, _("Disk identifier: 0x%08x\n"), mbr_get_id(cxt->firstsector)); } static int dos_create_disklabel(struct fdisk_context *cxt) { unsigned int id; assert(cxt); assert(cxt->label); assert(fdisk_is_disklabel(cxt, DOS)); /* random disk signature */ random_get_bytes(&id, sizeof(id)); fdisk_info(cxt, ("Building a new DOS disklabel with disk " "identifier 0x%08x."), id); dos_init(cxt); fdisk_zeroize_firstsector(cxt); fdisk_label_set_changed(cxt->label, 1); /* Generate an MBR ID for this disk */ mbr_set_id(cxt->firstsector, id); /* Put MBR signature */ mbr_set_magic(cxt->firstsector); return 0; } int dos_set_mbr_id(struct fdisk_context *cxt) { char *end = NULL, *str = NULL; unsigned int id, old; struct fdisk_dos_label *l = self_label(cxt); int rc; old = mbr_get_id(cxt->firstsector); rc = fdisk_ask_string(cxt, _("Enter of the new disk identifier"), &str); if (rc) return rc; errno = 0; id = strtoul(str, &end, 0); if (errno || str == end || (end && *end)) { fdisk_warnx(cxt, _("Incorrect value.")); return -EINVAL; } fdisk_info(cxt, _("Changing disk identifier from 0x%08x to 0x%08x."), old, id); mbr_set_id(cxt->firstsector, id); l->non_pt_changed = 1; fdisk_label_set_changed(cxt->label, 1); return 0; } static void get_partition_table_geometry(struct fdisk_context *cxt, unsigned int *ph, unsigned int *ps) { unsigned char *bufp = cxt->firstsector; struct dos_partition *p; int i, h, s, hh, ss; int first = 1; int bad = 0; hh = ss = 0; for (i=0; i<4; i++) { p = mbr_get_partition(bufp, i); if (p->sys_ind != 0) { h = p->eh + 1; s = (p->es & 077); if (first) { hh = h; ss = s; first = 0; } else if (hh != h || ss != s) bad = 1; } } if (!first && !bad) { *ph = hh; *ps = ss; } DBG(LABEL, dbgprint("DOS PT geometry: heads=%u, sectors=%u", *ph, *ps)); } static int dos_reset_alignment(struct fdisk_context *cxt) { assert(cxt); assert(cxt->label); assert(fdisk_is_disklabel(cxt, DOS)); /* overwrite necessary stuff by DOS deprecated stuff */ if (is_dos_compatible(cxt)) { if (cxt->geom.sectors) cxt->first_lba = cxt->geom.sectors; /* usually 63 */ cxt->grain = cxt->sector_size; /* usually 512 */ } return 0; } /* TODO: move to include/pt-dos.h and share with libblkid */ #define AIX_MAGIC_STRING "\xC9\xC2\xD4\xC1" #define AIX_MAGIC_STRLEN (sizeof(AIX_MAGIC_STRING) - 1) static int dos_probe_label(struct fdisk_context *cxt) { size_t i; unsigned int h = 0, s = 0; assert(cxt); assert(cxt->label); assert(fdisk_is_disklabel(cxt, DOS)); /* ignore disks with AIX magic number */ if (memcmp(cxt->firstsector, AIX_MAGIC_STRING, AIX_MAGIC_STRLEN) == 0) return 0; if (!mbr_is_valid_magic(cxt->firstsector)) return 0; dos_init(cxt); get_partition_table_geometry(cxt, &h, &s); if (h && s) { cxt->geom.heads = h; cxt->geom.sectors = s; } for (i = 0; i < 4; i++) { struct pte *pe = self_pte(cxt, i); if (!is_cleared_partition(pe->pt_entry)) cxt->label->nparts_cur++; if (IS_EXTENDED (pe->pt_entry->sys_ind)) { if (cxt->label->nparts_max != 4) fdisk_warnx(cxt, _( "Ignoring extra extended partition %zd"), i + 1); else read_extended(cxt, i); } } for (i = 3; i < cxt->label->nparts_max; i++) { struct pte *pe = self_pte(cxt, i); if (!mbr_is_valid_magic(pe->sectorbuffer)) { fdisk_info(cxt, _( "Invalid flag 0x%02x%02x of partition table %zd will " "be corrected by w(rite)"), pe->sectorbuffer[510], pe->sectorbuffer[511], i + 1); partition_set_changed(cxt, 1, 1); } } return 1; } /* * Avoid warning about DOS partitions when no DOS partition was changed. * Here a heuristic "is probably dos partition". * We might also do the opposite and warn in all cases except * for "is probably nondos partition". */ static int is_dos_partition(int t) { return (t == 1 || t == 4 || t == 6 || t == 0x0b || t == 0x0c || t == 0x0e || t == 0x11 || t == 0x12 || t == 0x14 || t == 0x16 || t == 0x1b || t == 0x1c || t == 0x1e || t == 0x24 || t == 0xc1 || t == 0xc4 || t == 0xc6); } static void set_partition(struct fdisk_context *cxt, int i, int doext, sector_t start, sector_t stop, int sysid) { struct pte *pe = self_pte(cxt, i); struct dos_partition *p; sector_t offset; if (doext) { struct fdisk_dos_label *l = self_label(cxt); p = pe->ex_entry; offset = l->ext_offset; } else { p = pe->pt_entry; offset = pe->offset; } p->boot_ind = 0; p->sys_ind = sysid; dos_partition_set_start(p, start - offset); dos_partition_set_size(p, stop - start + 1); if (!doext) { struct fdisk_parttype *t = fdisk_get_parttype_from_code(cxt, sysid); fdisk_info_new_partition(cxt, i + 1, start, stop, t); } if (is_dos_compatible(cxt) && (start/(cxt->geom.sectors*cxt->geom.heads) > 1023)) start = cxt->geom.heads*cxt->geom.sectors*1024 - 1; set_hsc(p->bh, p->bs, p->bc, start); if (is_dos_compatible(cxt) && (stop/(cxt->geom.sectors*cxt->geom.heads) > 1023)) stop = cxt->geom.heads*cxt->geom.sectors*1024 - 1; set_hsc(p->eh, p->es, p->ec, stop); partition_set_changed(cxt, i, 1); } static sector_t get_unused_start(struct fdisk_context *cxt, int part_n, sector_t start, sector_t first[], sector_t last[]) { size_t i; for (i = 0; i < cxt->label->nparts_max; i++) { sector_t lastplusoff; struct pte *pe = self_pte(cxt, i); if (start == pe->offset) start += cxt->first_lba; lastplusoff = last[i] + ((part_n < 4) ? 0 : cxt->first_lba); if (start >= first[i] && start <= lastplusoff) start = lastplusoff + 1; } return start; } static void fill_bounds(struct fdisk_context *cxt, sector_t *first, sector_t *last) { size_t i; struct pte *pe = self_pte(cxt, 0); struct dos_partition *p; for (i = 0; i < cxt->label->nparts_max; pe++,i++) { p = pe->pt_entry; if (!p->sys_ind || IS_EXTENDED (p->sys_ind)) { first[i] = 0xffffffff; last[i] = 0; } else { first[i] = get_abs_partition_start(pe); last[i] = first[i] + dos_partition_get_size(p) - 1; } } } static int add_partition(struct fdisk_context *cxt, int n, struct fdisk_parttype *t) { int sys, read = 0, rc; size_t i; struct fdisk_dos_label *l = self_label(cxt); struct dos_partition *p = self_partition(cxt, n); struct dos_partition *q = self_partition(cxt, l->ext_index); sector_t start, stop = 0, limit, temp, first[cxt->label->nparts_max], last[cxt->label->nparts_max]; sys = t ? t->type : LINUX_NATIVE; if (p && p->sys_ind) { fdisk_warnx(cxt, _("Partition %d is already defined. Delete " "it before re-adding it."), n + 1); return -EINVAL; } fill_bounds(cxt, first, last); if (n < 4) { start = cxt->first_lba; if (fdisk_context_use_cylinders(cxt) || !cxt->total_sectors) limit = cxt->geom.heads * cxt->geom.sectors * cxt->geom.cylinders - 1; else limit = cxt->total_sectors - 1; if (limit > UINT_MAX) limit = UINT_MAX; if (l->ext_offset) { first[l->ext_index] = l->ext_offset; last[l->ext_index] = dos_partition_get_start(q) + dos_partition_get_size(q) - 1; } } else { start = l->ext_offset + cxt->first_lba; limit = dos_partition_get_start(q) + dos_partition_get_size(q) - 1; } if (fdisk_context_use_cylinders(cxt)) for (i = 0; i < cxt->label->nparts_max; i++) first[i] = (cround(cxt, first[i]) - 1) * fdisk_context_get_units_per_sector(cxt); /* * Ask for first sector */ do { sector_t dflt, aligned; temp = start; dflt = start = get_unused_start(cxt, n, start, first, last); /* the default sector should be aligned and unused */ do { aligned = fdisk_align_lba_in_range(cxt, dflt, dflt, limit); dflt = get_unused_start(cxt, n, aligned, first, last); } while (dflt != aligned && dflt > aligned && dflt < limit); if (dflt >= limit) dflt = start; if (start > limit) break; if (start >= temp + fdisk_context_get_units_per_sector(cxt) && read) { fdisk_info(cxt, _("Sector %llu is already allocated"), temp); temp = start; read = 0; } if (!read && start == temp) { sector_t j = start; struct fdisk_ask *ask = fdisk_new_ask(); if (fdisk_context_use_cylinders(cxt)) fdisk_ask_set_query(ask, _("First cylinder")); else fdisk_ask_set_query(ask, _("First sector")); fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER); fdisk_ask_number_set_low(ask, cround(cxt, j)); fdisk_ask_number_set_default(ask, cround(cxt, dflt)); fdisk_ask_number_set_high(ask, cround(cxt, limit)); rc = fdisk_do_ask(cxt, ask); if (!rc) start = fdisk_ask_number_get_result(ask); fdisk_free_ask(ask); if (rc) return rc; if (fdisk_context_use_cylinders(cxt)) { start = (start - 1) * fdisk_context_get_units_per_sector(cxt); if (start < j) start = j; } read = 1; } } while (start != temp || !read); if (n > 4) { /* NOT for fifth partition */ struct pte *pe = self_pte(cxt, n); pe->offset = start - cxt->first_lba; if (pe->offset == l->ext_offset) { /* must be corrected */ pe->offset++; if (cxt->first_lba == 1) start++; } } for (i = 0; i < cxt->label->nparts_max; i++) { struct pte *pe = self_pte(cxt, i); if (start < pe->offset && limit >= pe->offset) limit = pe->offset - 1; if (start < first[i] && limit >= first[i]) limit = first[i] - 1; } if (start > limit) { fdisk_info(cxt, _("No free sectors available")); if (n > 4) cxt->label->nparts_max--; return -ENOSPC; } if (cround(cxt, start) == cround(cxt, limit)) { stop = limit; } else { /* * Ask for last sector */ struct fdisk_ask *ask = fdisk_new_ask(); fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET); if (fdisk_context_use_cylinders(cxt)) { fdisk_ask_set_query(ask, _("Last cylinder, +cylinders or +size{K,M,G,T,P}")); fdisk_ask_number_set_unit(ask, cxt->sector_size * fdisk_context_get_units_per_sector(cxt)); } else { fdisk_ask_set_query(ask, _("Last sector, +sectors or +size{K,M,G,T,P}")); fdisk_ask_number_set_unit(ask,cxt->sector_size); } fdisk_ask_number_set_low(ask, cround(cxt, start)); fdisk_ask_number_set_default(ask, cround(cxt, limit)); fdisk_ask_number_set_high(ask, cround(cxt, limit)); fdisk_ask_number_set_base(ask, cround(cxt, start)); /* base for relative input */ rc = fdisk_do_ask(cxt, ask); if (rc) { fdisk_free_ask(ask); return rc; } stop = fdisk_ask_number_get_result(ask); if (fdisk_ask_number_is_relative(ask) && alignment_required(cxt)) { /* the last sector has not been exactly requested (but * defined by +size{K,M,G} convention), so be smart and * align the end of the partition. The next partition * will start at phy.block boundary. */ stop = fdisk_align_lba_in_range(cxt, stop, start, limit) - 1; if (stop > limit) stop = limit; } fdisk_free_ask(ask); } set_partition(cxt, n, 0, start, stop, sys); if (n > 4) { struct pte *pe = self_pte(cxt, n); set_partition(cxt, n - 1, 1, pe->offset, stop, EXTENDED); } if (IS_EXTENDED (sys)) { struct pte *pe4 = self_pte(cxt, 4); struct pte *pen = self_pte(cxt, n); l->ext_index = n; pen->ex_entry = p; pe4->offset = l->ext_offset = start; pe4->sectorbuffer = xcalloc(1, cxt->sector_size); pe4->pt_entry = mbr_get_partition(pe4->sectorbuffer, 0); pe4->ex_entry = pe4->pt_entry + 1; partition_set_changed(cxt, 4, 1); cxt->label->nparts_max = 5; } fdisk_label_set_changed(cxt->label, 1); return 0; } static int add_logical(struct fdisk_context *cxt) { struct dos_partition *p4 = self_partition(cxt, 4); assert(cxt); assert(cxt->label); if (cxt->label->nparts_max > 5 || p4->sys_ind) { struct pte *pe = self_pte(cxt, cxt->label->nparts_max); pe->sectorbuffer = calloc(1, cxt->sector_size); if (!pe->sectorbuffer) return -ENOMEM; pe->pt_entry = mbr_get_partition(pe->sectorbuffer, 0); pe->ex_entry = pe->pt_entry + 1; pe->offset = 0; partition_set_changed(cxt, cxt->label->nparts_max, 1); cxt->label->nparts_max++; } fdisk_info(cxt, _("Adding logical partition %zd"), cxt->label->nparts_max); return add_partition(cxt, cxt->label->nparts_max - 1, NULL); } static void check(struct fdisk_context *cxt, size_t n, unsigned int h, unsigned int s, unsigned int c, unsigned int start) { unsigned int total, real_s, real_c; real_s = sector(s) - 1; real_c = cylinder(s, c); total = (real_c * cxt->geom.sectors + real_s) * cxt->geom.heads + h; if (!total) fdisk_warnx(cxt, _("Partition %zd: contains sector 0"), n); if (h >= cxt->geom.heads) fdisk_warnx(cxt, _("Partition %zd: head %d greater than " "maximum %d"), n, h + 1, cxt->geom.heads); if (real_s >= cxt->geom.sectors) fdisk_warnx(cxt, _("Partition %zd: sector %d greater than " "maximum %llu"), n, s, cxt->geom.sectors); if (real_c >= cxt->geom.cylinders) fdisk_warnx(cxt, _("Partition %zd: cylinder %d greater than " "maximum %llu"), n, real_c + 1, cxt->geom.cylinders); if (cxt->geom.cylinders <= 1024 && start != total) fdisk_warnx(cxt, _("Partition %zd: previous sectors %d " "disagrees with total %d"), n, start, total); } /* check_consistency() and long2chs() added Sat Mar 6 12:28:16 1993, * faith@cs.unc.edu, based on code fragments from pfdisk by Gordon W. Ross, * Jan. 1990 (version 1.2.1 by Gordon W. Ross Aug. 1990; Modified by S. * Lubkin Oct. 1991). */ static void long2chs(struct fdisk_context *cxt, unsigned long ls, unsigned int *c, unsigned int *h, unsigned int *s) { int spc = cxt->geom.heads * cxt->geom.sectors; *c = ls / spc; ls = ls % spc; *h = ls / cxt->geom.sectors; *s = ls % cxt->geom.sectors + 1; /* sectors count from 1 */ } static void check_consistency(struct fdisk_context *cxt, struct dos_partition *p, size_t partition) { unsigned int pbc, pbh, pbs; /* physical beginning c, h, s */ unsigned int pec, peh, pes; /* physical ending c, h, s */ unsigned int lbc, lbh, lbs; /* logical beginning c, h, s */ unsigned int lec, leh, les; /* logical ending c, h, s */ if (!is_dos_compatible(cxt)) return; if (!cxt->geom.heads || !cxt->geom.sectors || (partition >= 4)) return; /* do not check extended partitions */ /* physical beginning c, h, s */ pbc = (p->bc & 0xff) | ((p->bs << 2) & 0x300); pbh = p->bh; pbs = p->bs & 0x3f; /* physical ending c, h, s */ pec = (p->ec & 0xff) | ((p->es << 2) & 0x300); peh = p->eh; pes = p->es & 0x3f; /* compute logical beginning (c, h, s) */ long2chs(cxt, dos_partition_get_start(p), &lbc, &lbh, &lbs); /* compute logical ending (c, h, s) */ long2chs(cxt, dos_partition_get_start(p) + dos_partition_get_size(p) - 1, &lec, &leh, &les); /* Same physical / logical beginning? */ if (cxt->geom.cylinders <= 1024 && (pbc != lbc || pbh != lbh || pbs != lbs)) { fdisk_warnx(cxt, _("Partition %zd: different physical/logical " "beginnings (non-Linux?): " "phys=(%d, %d, %d), logical=(%d, %d, %d)"), partition + 1, pbc, pbh, pbs, lbc, lbh, lbs); } /* Same physical / logical ending? */ if (cxt->geom.cylinders <= 1024 && (pec != lec || peh != leh || pes != les)) { fdisk_warnx(cxt, _("Partition %zd: different physical/logical " "endings: phys=(%d, %d, %d), logical=(%d, %d, %d)"), partition + 1, pec, peh, pes, lec, leh, les); } /* Ending on cylinder boundary? */ if (peh != (cxt->geom.heads - 1) || pes != cxt->geom.sectors) { fdisk_warnx(cxt, _("Partition %zd: does not end on " "cylinder boundary."), partition + 1); } } static int dos_verify_disklabel(struct fdisk_context *cxt) { size_t i, j; sector_t total = 1, n_sectors = cxt->total_sectors; unsigned long long first[cxt->label->nparts_max], last[cxt->label->nparts_max]; struct dos_partition *p; struct fdisk_dos_label *l = self_label(cxt); assert(fdisk_is_disklabel(cxt, DOS)); fill_bounds(cxt, first, last); for (i = 0; i < cxt->label->nparts_max; i++) { struct pte *pe = self_pte(cxt, i); p = self_partition(cxt, i); if (p->sys_ind && !IS_EXTENDED (p->sys_ind)) { check_consistency(cxt, p, i); fdisk_warn_alignment(cxt, get_abs_partition_start(pe), i); if (get_abs_partition_start(pe) < first[i]) fdisk_warnx(cxt, _( "Partition %zd: bad start-of-data."), i + 1); check(cxt, i + 1, p->eh, p->es, p->ec, last[i]); total += last[i] + 1 - first[i]; for (j = 0; j < i; j++) { if ((first[i] >= first[j] && first[i] <= last[j]) || ((last[i] <= last[j] && last[i] >= first[j]))) { fdisk_warnx(cxt, _("Partition %zd: " "overlaps partition %zd."), j + 1, i + 1); total += first[i] >= first[j] ? first[i] : first[j]; total -= last[i] <= last[j] ? last[i] : last[j]; } } } } if (l->ext_offset) { p = self_partition(cxt, l->ext_index); sector_t e_last = dos_partition_get_start(p) + dos_partition_get_size(p) - 1; for (i = 4; i < cxt->label->nparts_max; i++) { total++; p = self_partition(cxt, i); if (!p->sys_ind) { if (i != 4 || i + 1 < cxt->label->nparts_max) fdisk_warnx(cxt, _("Partition %zd: empty"), i + 1); } else if (first[i] < l->ext_offset || last[i] > e_last) { fdisk_warnx(cxt, _("Logical partition %zd: " "not entirely in partition %zd"), i + 1, l->ext_index + 1); } } } if (total > n_sectors) fdisk_warnx(cxt, _("Total allocated sectors %llu greater " "than the maximum %llu."), total, n_sectors); else if (total < n_sectors) fdisk_warnx(cxt, _("Remaining %lld unallocated %ld-byte " "sectors"), n_sectors - total, cxt->sector_size); return 0; } /* * Ask the user for new partition type information (logical, extended). * This function calls the actual partition adding logic - add_partition. * * API callback. */ static int dos_add_partition( struct fdisk_context *cxt, size_t partnum __attribute__ ((__unused__)), struct fdisk_parttype *t) { size_t i, free_primary = 0; int rc = 0; struct fdisk_dos_label *l = self_label(cxt); assert(cxt); assert(cxt->label); assert(fdisk_is_disklabel(cxt, DOS)); for (i = 0; i < 4; i++) { struct dos_partition *p = self_partition(cxt, i); free_primary += !p->sys_ind; } if (!free_primary && cxt->label->nparts_max >= MAXIMUM_PARTS) { fdisk_info(cxt, _("The maximum number of partitions has " "been created.")); return -EINVAL; } rc = 1; if (!free_primary) { if (l->ext_offset) { fdisk_info(cxt, _("All primary partitions are in use.")); rc = add_logical(cxt); } else fdisk_info(cxt, _("If you want to create more than " "four partitions, you must replace a" "primary partition with an extended " "partition first.")); } else if (cxt->label->nparts_max >= MAXIMUM_PARTS) { int j; fdisk_info(cxt, _("All logical partitions are in use. " "Adding a primary partition.")); j = get_partition_unused_primary(cxt); if (j >= 0) rc = add_partition(cxt, j, t); } else { char buf[16]; char c, prompt[LINE_LENGTH]; int dflt; dflt = (free_primary == 1 && !l->ext_offset) ? 'e' : 'p'; snprintf(prompt, sizeof(prompt), _("Partition type:\n" " p primary (%zd primary, %d extended, %zd free)\n" "%s\n" "Select (default %c): "), 4 - (l->ext_offset ? 1 : 0) - free_primary, l->ext_offset ? 1 : 0, free_primary, l->ext_offset ? _(" l logical (numbered from 5)") : _(" e extended"), dflt); rc = get_user_reply(cxt, prompt, buf, sizeof(buf)); if (rc) return rc; if (!buf[0]) { c = dflt; printf(_("Using default response %c\n"), c); } else c = tolower(buf[0]); if (c == 'p') { int j = get_partition_unused_primary(cxt); if (j >= 0) rc = add_partition(cxt, j, t); goto done; } else if (c == 'l' && l->ext_offset) { rc = add_logical(cxt); goto done; } else if (c == 'e' && !l->ext_offset) { int j = get_partition_unused_primary(cxt); if (j >= 0) { t = fdisk_get_parttype_from_code(cxt, EXTENDED); rc = add_partition(cxt, j, t); } goto done; } else fdisk_warnx(cxt, _("Invalid partition type `%c'"), c); } done: if (rc == 0) cxt->label->nparts_cur++; return rc; } static int write_sector(struct fdisk_context *cxt, sector_t secno, unsigned char *buf) { int rc; rc = seek_sector(cxt, secno); if (rc != 0) { fdisk_warn(cxt, _("Write sector %jd failed: seek failed"), (uintmax_t) secno); return rc; } if (write(cxt->dev_fd, buf, cxt->sector_size) != (ssize_t) cxt->sector_size) return -errno; return 0; } static int dos_write_disklabel(struct fdisk_context *cxt) { struct fdisk_dos_label *l = self_label(cxt); size_t i; int rc = 0, mbr_changed = 0; assert(cxt); assert(cxt->label); assert(fdisk_is_disklabel(cxt, DOS)); mbr_changed = l->non_pt_changed; /* MBR (primary partitions) */ if (!mbr_changed) { for (i = 0; i < 4; i++) { struct pte *pe = self_pte(cxt, i); if (pe->changed) mbr_changed = 1; } } if (mbr_changed) { mbr_set_magic(cxt->firstsector); rc = write_sector(cxt, 0, cxt->firstsector); if (rc) goto done; } /* EBR (logical partitions) */ for (i = 4; i < cxt->label->nparts_max; i++) { struct pte *pe = self_pte(cxt, i); if (pe->changed) { mbr_set_magic(pe->sectorbuffer); rc = write_sector(cxt, pe->offset, pe->sectorbuffer); if (rc) goto done; } } done: return rc; } static struct fdisk_parttype *dos_get_parttype( struct fdisk_context *cxt, size_t partnum) { struct fdisk_parttype *t; struct dos_partition *p; assert(cxt); assert(cxt->label); assert(fdisk_is_disklabel(cxt, DOS)); if (partnum >= cxt->label->nparts_max) return NULL; p = self_partition(cxt, partnum); t = fdisk_get_parttype_from_code(cxt, p->sys_ind); if (!t) t = fdisk_new_unknown_parttype(p->sys_ind, NULL); return t; } static int dos_set_parttype( struct fdisk_context *cxt, size_t partnum, struct fdisk_parttype *t) { struct dos_partition *p; assert(cxt); assert(cxt->label); assert(fdisk_is_disklabel(cxt, DOS)); if (partnum >= cxt->label->nparts_max || !t || t->type > UINT8_MAX) return -EINVAL; p = self_partition(cxt, partnum); if (t->type == p->sys_ind) return 0; if (IS_EXTENDED(p->sys_ind) || IS_EXTENDED(t->type)) { fdisk_warnx(cxt, _("You cannot change a partition into an " "extended one or vice versa. Delete it first.")); return -EINVAL; } if (is_dos_partition(t->type) || is_dos_partition(p->sys_ind)) fdisk_info(cxt, _("If you have created or modified any DOS 6.x " "partitions, please see the fdisk documantation for additional " "information.")); p->sys_ind = t->type; partition_set_changed(cxt, partnum, 1); return 0; } /* * Check whether partition entries are ordered by their starting positions. * Return 0 if OK. Return i if partition i should have been earlier. * Two separate checks: primary and logical partitions. */ static int wrong_p_order(struct fdisk_context *cxt, size_t *prev) { size_t last_p_start_pos = 0, p_start_pos; size_t i, last_i = 0; for (i = 0 ; i < cxt->label->nparts_max; i++) { struct pte *pe = self_pte(cxt, i); struct dos_partition *p = pe->pt_entry; if (i == 4) { last_i = 4; last_p_start_pos = 0; } if (p->sys_ind) { p_start_pos = get_abs_partition_start(pe); if (last_p_start_pos > p_start_pos) { if (prev) *prev = last_i; return i; } last_p_start_pos = p_start_pos; last_i = i; } } return 0; } static int is_garbage_table(struct fdisk_context *cxt) { size_t i; for (i = 0; i < 4; i++) { struct dos_partition *p = self_partition(cxt, i); if (p->boot_ind != 0 && p->boot_ind != 0x80) return 1; } return 0; } int dos_list_table(struct fdisk_context *cxt, int xtra __attribute__ ((__unused__))) { size_t i, w; assert(cxt); assert(cxt->label); assert(fdisk_is_disklabel(cxt, DOS)); if (is_garbage_table(cxt)) { fdisk_warnx(cxt, _( "This doesn't look like a partition table " "Probably you selected the wrong device.")); } /* Heuristic: we list partition 3 of /dev/foo as /dev/foo3, but if the device name ends in a digit, say /dev/foo1, then the partition is called /dev/foo1p3. */ w = strlen(cxt->dev_path); if (w && isdigit(cxt->dev_path[w-1])) w++; if (w < 5) w = 5; printf(_("%*s Boot Start End Blocks Id System\n"), (int) w + 1, _("Device")); for (i = 0; i < cxt->label->nparts_max; i++) { struct pte *pe = self_pte(cxt, i); struct dos_partition *p = pe->pt_entry; if (p && !is_cleared_partition(p)) { unsigned int psects = dos_partition_get_size(p); unsigned int pblocks = psects; unsigned int podd = 0; struct fdisk_parttype *type = fdisk_get_parttype_from_code(cxt, p->sys_ind); if (cxt->sector_size < 1024) { pblocks /= (1024 / cxt->sector_size); podd = psects % (1024 / cxt->sector_size); } if (cxt->sector_size > 1024) pblocks *= (cxt->sector_size / 1024); printf( "%s %c %11lu %11lu %11lu%c %2x %s\n", partname(cxt->dev_path, i+1, w+2), /* boot flag */ !p->boot_ind ? ' ' : p->boot_ind == ACTIVE_FLAG ? '*' : '?', /* start */ (unsigned long) cround(cxt, get_abs_partition_start(pe)), /* end */ (unsigned long) cround(cxt, get_abs_partition_start(pe) + psects - (psects ? 1 : 0)), /* odd flag on end */ (unsigned long) pblocks, podd ? '+' : ' ', /* type id */ p->sys_ind, /* type name */ type ? type->name : _("Unknown")); check_consistency(cxt, p, i); fdisk_warn_alignment(cxt, get_abs_partition_start(pe), i); } } /* Is partition table in disk order? It need not be, but... */ /* partition table entries are not checked for correct order if this is a sgi, sun labeled disk... */ if (wrong_p_order(cxt, NULL)) fdisk_info(cxt, _("Partition table entries are not in " "disk order.")); return 0; } /* * TODO: merge into dos_list_table */ void dos_list_table_expert(struct fdisk_context *cxt, int extend) { struct pte *pe; struct dos_partition *p; size_t i; printf(_("\nDisk %s: %d heads, %llu sectors, %llu cylinders\n\n"), cxt->dev_path, cxt->geom.heads, cxt->geom.sectors, cxt->geom.cylinders); printf(_("Nr AF Hd Sec Cyl Hd Sec Cyl Start Size ID\n")); for (i = 0 ; i < cxt->label->nparts_max; i++) { pe = self_pte(cxt, i); p = (extend ? pe->ex_entry : pe->pt_entry); if (p != NULL) { printf("%2zd %02x%4d%4d%5d%4d%4d%5d%11lu%11lu %02x\n", i + 1, p->boot_ind, p->bh, sector(p->bs), cylinder(p->bs, p->bc), p->eh, sector(p->es), cylinder(p->es, p->ec), (unsigned long) dos_partition_get_start(p), (unsigned long) dos_partition_get_size(p), p->sys_ind); if (p->sys_ind) { check_consistency(cxt, p, i); fdisk_warn_alignment(cxt, get_abs_partition_start(pe), i); } } } } /* * Fix the chain of logicals. * ext_offset is unchanged, the set of sectors used is unchanged * The chain is sorted so that sectors increase, and so that * starting sectors increase. * * After this it may still be that cfdisk doesn't like the table. * (This is because cfdisk considers expanded parts, from link to * end of partition, and these may still overlap.) * Now * sfdisk /dev/hda > ohda; sfdisk /dev/hda < ohda * may help. */ static void fix_chain_of_logicals(struct fdisk_context *cxt) { struct fdisk_dos_label *l = self_label(cxt); size_t j, oj, ojj, sj, sjj; struct dos_partition *pj,*pjj,tmp; /* Stage 1: sort sectors but leave sector of part 4 */ /* (Its sector is the global ext_offset.) */ stage1: for (j = 5; j < cxt->label->nparts_max - 1; j++) { oj = l->ptes[j].offset; ojj = l->ptes[j + 1].offset; if (oj > ojj) { l->ptes[j].offset = ojj; l->ptes[j + 1].offset = oj; pj = l->ptes[j].pt_entry; dos_partition_set_start(pj, dos_partition_get_start(pj)+oj-ojj); pjj = l->ptes[j + 1].pt_entry; dos_partition_set_start(pjj, dos_partition_get_start(pjj)+ojj-oj); dos_partition_set_start(l->ptes[j - 1].ex_entry, ojj - l->ext_offset); dos_partition_set_start(l->ptes[j].ex_entry, oj - l->ext_offset); goto stage1; } } /* Stage 2: sort starting sectors */ stage2: for (j = 4; j < cxt->label->nparts_max - 1; j++) { pj = l->ptes[j].pt_entry; pjj = l->ptes[j + 1].pt_entry; sj = dos_partition_get_start(pj); sjj = dos_partition_get_start(pjj); oj = l->ptes[j].offset; ojj = l->ptes[j+1].offset; if (oj+sj > ojj+sjj) { tmp = *pj; *pj = *pjj; *pjj = tmp; dos_partition_set_start(pj, ojj+sjj-oj); dos_partition_set_start(pjj, oj+sj-ojj); goto stage2; } } /* Probably something was changed */ for (j = 4; j < cxt->label->nparts_max; j++) l->ptes[j].changed = 1; } void dos_fix_partition_table_order(struct fdisk_context *cxt) { struct pte *pei, *pek; size_t i,k; if (!wrong_p_order(cxt, NULL)) { fdisk_info(cxt, _("Nothing to do. Ordering is correct already.")); return; } 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); printf(_("Done.\n")); } void dos_move_begin(struct fdisk_context *cxt, int i) { struct pte *pe = self_pte(cxt, i); struct dos_partition *p = pe->pt_entry; unsigned int new, free_start, curr_start, last; uintmax_t res = 0; size_t x; assert(cxt); assert(fdisk_is_disklabel(cxt, DOS)); if (!p->sys_ind || !dos_partition_get_size(p) || IS_EXTENDED (p->sys_ind)) { fdisk_warn(cxt, _("Partition %d: no data area."), i + 1); return; } /* the default start is at the second sector of the disk or at the * second sector of the extended partition */ free_start = pe->offset ? pe->offset + 1 : 1; curr_start = get_abs_partition_start(pe); /* look for a free space before the current start of the partition */ for (x = 0; x < cxt->label->nparts_max; x++) { unsigned int end; struct pte *prev_pe = self_pte(cxt, x); struct dos_partition *prev_p = prev_pe->pt_entry; if (!prev_p) continue; end = get_abs_partition_start(prev_pe) + dos_partition_get_size(prev_p); if (!is_cleared_partition(prev_p) && end > free_start && end <= curr_start) free_start = end; } last = get_abs_partition_start(pe) + dos_partition_get_size(p) - 1; if (fdisk_ask_number(cxt, free_start, curr_start, last, _("New beginning of data"), &res)) return; 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); } } static int dos_get_partition_status( struct fdisk_context *cxt, size_t i, int *status) { struct dos_partition *p; assert(cxt); assert(cxt->label); assert(fdisk_is_disklabel(cxt, DOS)); if (!status || i >= cxt->label->nparts_max) return -EINVAL; p = self_partition(cxt, i); if (p && !is_cleared_partition(p)) *status = FDISK_PARTSTAT_USED; else *status = FDISK_PARTSTAT_NONE; return 0; } static int dos_toggle_partition_flag( struct fdisk_context *cxt, size_t i, unsigned long flag) { struct dos_partition *p; assert(cxt); assert(cxt->label); assert(fdisk_is_disklabel(cxt, DOS)); if (i >= cxt->label->nparts_max) return -EINVAL; p = self_partition(cxt, i); switch (flag) { case DOS_FLAG_ACTIVE: if (IS_EXTENDED(p->sys_ind) && !p->boot_ind) fdisk_warnx(cxt, _("Partition %d: is an extended " "partition."), i + 1); p->boot_ind = (p->boot_ind ? 0 : ACTIVE_FLAG); partition_set_changed(cxt, i, 1); break; default: return 1; } return 0; } static const struct fdisk_label_operations dos_operations = { .probe = dos_probe_label, .write = dos_write_disklabel, .verify = dos_verify_disklabel, .create = dos_create_disklabel, .part_add = dos_add_partition, .part_delete = dos_delete_partition, .part_get_type = dos_get_parttype, .part_set_type = dos_set_parttype, .part_toggle_flag = dos_toggle_partition_flag, .part_get_status = dos_get_partition_status, .reset_alignment = dos_reset_alignment, }; /* * allocates DOS in-memory stuff */ struct fdisk_label *fdisk_new_dos_label(struct fdisk_context *cxt) { struct fdisk_label *lb; struct fdisk_dos_label *dos; assert(cxt); dos = calloc(1, sizeof(*dos)); if (!dos) return NULL; /* initialize generic part of the driver */ lb = (struct fdisk_label *) dos; lb->name = "dos"; lb->id = FDISK_DISKLABEL_DOS; lb->op = &dos_operations; lb->parttypes = dos_parttypes; lb->nparttypes = ARRAY_SIZE(dos_parttypes); lb->flags |= FDISK_LABEL_FL_ADDPART_NOPARTNO; return lb; } /* * Public label specific functions */ int fdisk_dos_enable_compatible(struct fdisk_label *lb, int enable) { struct fdisk_dos_label *dos = (struct fdisk_dos_label *) lb; if (!lb) return -EINVAL; dos->compatible = enable; if (enable) lb->flags |= FDISK_LABEL_FL_REQUIRE_GEOMETRY; return 0; } int fdisk_dos_is_compatible(struct fdisk_label *lb) { return ((struct fdisk_dos_label *) lb)->compatible; }