blob: 9a31fc1547b31a20c8555bab7ae6703deb9a1b2c [file] [log] [blame]
/*
libparted - a library for manipulating disk partitions
Copyright (C) 1999, 2000, 2001, 2004, 2005, 2007
Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <config.h>
#include <sys/time.h>
#include <parted/parted.h>
#include <parted/debug.h>
#include <parted/endian.h>
#if ENABLE_NLS
# include <libintl.h>
# define _(String) dgettext (PACKAGE, String)
#else
# define _(String) (String)
#endif /* ENABLE_NLS */
/* this MBR boot code is loaded into 0000:7c00 by the BIOS. See mbr.s for
* the source, and how to build it
*/
static const char MBR_BOOT_CODE[] = {
0xfa, 0xb8, 0x00, 0x10, 0x8e, 0xd0, 0xbc, 0x00,
0xb0, 0xb8, 0x00, 0x00, 0x8e, 0xd8, 0x8e, 0xc0,
0xfb, 0xbe, 0x00, 0x7c, 0xbf, 0x00, 0x06, 0xb9,
0x00, 0x02, 0xf3, 0xa4, 0xea, 0x21, 0x06, 0x00,
0x00, 0xbe, 0xbe, 0x07, 0x38, 0x04, 0x75, 0x0b,
0x83, 0xc6, 0x10, 0x81, 0xfe, 0xfe, 0x07, 0x75,
0xf3, 0xeb, 0x16, 0xb4, 0x02, 0xb0, 0x01, 0xbb,
0x00, 0x7c, 0xb2, 0x80, 0x8a, 0x74, 0x01, 0x8b,
0x4c, 0x02, 0xcd, 0x13, 0xea, 0x00, 0x7c, 0x00,
0x00, 0xeb, 0xfe
};
#define MSDOS_MAGIC 0xAA55
#define PARTITION_MAGIC_MAGIC 0xf6f6
#define PARTITION_EMPTY 0x00
#define PARTITION_FAT12 0x01
#define PARTITION_FAT16_SM 0x04
#define PARTITION_DOS_EXT 0x05
#define PARTITION_FAT16 0x06
#define PARTITION_NTFS 0x07
#define PARTITION_HPFS 0x07
#define PARTITION_FAT32 0x0b
#define PARTITION_FAT32_LBA 0x0c
#define PARTITION_FAT16_LBA 0x0e
#define PARTITION_EXT_LBA 0x0f
#define PART_FLAG_HIDDEN 0x10 /* Valid for FAT/NTFS only */
#define PARTITION_FAT12_H (PARTITION_FAT12 | PART_FLAG_HIDDEN)
#define PARTITION_FAT16_SM_H (PARTITION_FAT16_SM | PART_FLAG_HIDDEN)
#define PARTITION_DOS_EXT_H (PARTITION_DOS_EXT | PART_FLAG_HIDDEN)
#define PARTITION_FAT16_H (PARTITION_FAT16 | PART_FLAG_HIDDEN)
#define PARTITION_NTFS_H (PARTITION_NTFS | PART_FLAG_HIDDEN)
#define PARTITION_FAT32_H (PARTITION_FAT32 | PART_FLAG_HIDDEN)
#define PARTITION_FAT32_LBA_H (PARTITION_FAT32_LBA | PART_FLAG_HIDDEN)
#define PARTITION_FAT16_LBA_H (PARTITION_FAT16_LBA | PART_FLAG_HIDDEN)
#define PARTITION_COMPAQ_DIAG 0x12
#define PARTITION_LDM 0x42
#define PARTITION_LINUX_SWAP 0x82
#define PARTITION_LINUX 0x83
#define PARTITION_LINUX_EXT 0x85
#define PARTITION_LINUX_LVM 0x8e
#define PARTITION_SUN_UFS 0xbf
#define PARTITION_DELL_DIAG 0xde
#define PARTITION_GPT 0xee
#define PARTITION_PALO 0xf0
#define PARTITION_PREP 0x41
#define PARTITION_LINUX_RAID 0xfd
#define PARTITION_LINUX_LVM_OLD 0xfe
/* This constant contains the maximum cylinder number that can be represented
* in (C,H,S) notation. Higher cylinder numbers are reserved for
* "too big" indicators (in which case only LBA addressing can be used).
* Some partition tables in the wild indicate this number is 1021.
* (i.e. 1022 is sometimes used to indicate "use LBA").
*/
#define MAX_CHS_CYLINDER 1021
typedef struct _DosRawPartition DosRawPartition;
typedef struct _DosRawTable DosRawTable;
/* note: lots of bit-bashing here, thus, you shouldn't look inside it.
* Use chs_to_sector() and sector_to_chs() instead.
*/
typedef struct {
uint8_t head;
uint8_t sector;
uint8_t cylinder;
} __attribute__((packed)) RawCHS;
/* ripped from Linux source */
struct _DosRawPartition {
uint8_t boot_ind; /* 00: 0x80 - active */
RawCHS chs_start; /* 01: */
uint8_t type; /* 04: partition type */
RawCHS chs_end; /* 05: */
uint32_t start; /* 08: starting sector counting from 0 */
uint32_t length; /* 0c: nr of sectors in partition */
} __attribute__((packed));
struct _DosRawTable {
char boot_code [440];
uint32_t mbr_signature; /* really a unique ID */
uint16_t Unknown;
DosRawPartition partitions [4];
uint16_t magic;
} __attribute__((packed));
/* OrigState is information we want to preserve about the partition for
* dealing with CHS issues
*/
typedef struct {
PedGeometry geom;
DosRawPartition raw_part;
PedSector lba_offset; /* needed for computing start/end for
* logical partitions */
} OrigState;
typedef struct {
unsigned char system;
int boot;
int hidden;
int raid;
int lvm;
int lba;
int palo;
int prep;
OrigState* orig; /* used for CHS stuff */
} DosPartitionData;
static PedDiskType msdos_disk_type;
static int
msdos_probe (const PedDevice *dev)
{
PedDiskType* disk_type;
DosRawTable part_table;
int i;
PED_ASSERT (dev != NULL, return 0);
if (dev->sector_size != 512)
return 0;
if (!ped_device_read (dev, &part_table, 0, 1))
return 0;
/* check magic */
if (PED_LE16_TO_CPU (part_table.magic) != MSDOS_MAGIC)
return 0;
/* if this is a FAT fs, fail here. Note that the Smart Boot Manager
* Loader (SBML) signature indicates a partition table, not a file
* system.
*/
/* check below commented out to allow FAT fs images without a MBR */
/* if ((!strncmp (part_table.boot_code + 0x36, "FAT", 3)
&& strncmp (part_table.boot_code + 0x40, "SBML", 4) != 0)
|| !strncmp (part_table.boot_code + 0x52, "FAT", 3))
return 0;
*/
/* If this is a GPT disk, fail here */
for (i = 0; i < 4; i++) {
if (part_table.partitions[i].type == PARTITION_GPT)
return 0;
}
/* If this is an AIX Physical Volume, fail here. IBMA in EBCDIC */
if (part_table.boot_code[0] == (char) 0xc9 &&
part_table.boot_code[1] == (char) 0xc2 &&
part_table.boot_code[2] == (char) 0xd4 &&
part_table.boot_code[3] == (char) 0xc1)
return 0;
#ifdef ENABLE_PC98
/* HACK: it's impossible to tell PC98 and msdos disk labels apart.
* Someone made the signatures the same (very clever). Since
* PC98 has some idiosyncracies with it's boot-loader, it's detection
* is more reliable */
disk_type = ped_disk_type_get ("pc98");
if (disk_type && disk_type->ops->probe (dev))
return 0;
#endif /* ENABLE_PC98 */
return 1;
}
static PedDisk*
msdos_alloc (const PedDevice* dev)
{
PedDisk* disk;
PED_ASSERT (dev != NULL, return NULL);
disk = _ped_disk_alloc ((PedDevice*)dev, &msdos_disk_type);
if (disk)
disk->disk_specific = NULL;
return disk;
}
static PedDisk*
msdos_duplicate (const PedDisk* disk)
{
PedDisk* new_disk;
new_disk = ped_disk_new_fresh (disk->dev, &msdos_disk_type);
if (!new_disk)
return NULL;
new_disk->disk_specific = NULL;
return new_disk;
}
static void
msdos_free (PedDisk* disk)
{
PED_ASSERT (disk != NULL, return);
_ped_disk_free (disk);
}
#ifndef DISCOVER_ONLY
static int
msdos_clobber (PedDevice* dev)
{
DosRawTable table;
PED_ASSERT (dev != NULL, return 0);
PED_ASSERT (msdos_probe (dev), return 0);
if (!ped_device_read (dev, &table, 0, 1))
return 0;
table.magic = 0;
return ped_device_write (dev, (void*) &table, 0, 1);
}
#endif /* !DISCOVER_ONLY */
static int
chs_get_cylinder (const RawCHS* chs)
{
return chs->cylinder + ((chs->sector >> 6) << 8);
}
static int
chs_get_head (const RawCHS* chs)
{
return chs->head;
}
/* counts from 0 */
static int
chs_get_sector (const RawCHS* chs)
{
return (chs->sector & 0x3f) - 1;
}
static PedSector
chs_to_sector (const PedDevice* dev, const PedCHSGeometry *bios_geom,
const RawCHS* chs)
{
PedSector c; /* not measured in sectors, but need */
PedSector h; /* lots of bits */
PedSector s;
PED_ASSERT (bios_geom != NULL, return 0);
PED_ASSERT (chs != NULL, return 0);
c = chs_get_cylinder (chs);
h = chs_get_head (chs);
s = chs_get_sector (chs);
if (c > MAX_CHS_CYLINDER) /* MAGIC: C/H/S is irrelevant */
return 0;
if (s < 0)
return 0;
return ((c * bios_geom->heads + h) * bios_geom->sectors + s)
* (dev->sector_size / 512);
}
static void
sector_to_chs (const PedDevice* dev, const PedCHSGeometry* bios_geom,
PedSector sector, RawCHS* chs)
{
PedSector real_c, real_h, real_s;
PED_ASSERT (dev != NULL, return);
PED_ASSERT (chs != NULL, return);
if (!bios_geom)
bios_geom = &dev->bios_geom;
sector /= (dev->sector_size / 512);
real_c = sector / (bios_geom->heads * bios_geom->sectors);
real_h = (sector / bios_geom->sectors) % bios_geom->heads;
real_s = sector % bios_geom->sectors;
if (real_c > MAX_CHS_CYLINDER) {
real_c = 1023;
real_h = bios_geom->heads - 1;
real_s = bios_geom->sectors - 1;
}
chs->cylinder = real_c % 0x100;
chs->head = real_h;
chs->sector = real_s + 1 + (real_c >> 8 << 6);
}
static PedSector
legacy_start (const PedDisk* disk, const PedCHSGeometry* bios_geom,
const DosRawPartition* raw_part)
{
PED_ASSERT (disk != NULL, return 0);
PED_ASSERT (raw_part != NULL, return 0);
return chs_to_sector (disk->dev, bios_geom, &raw_part->chs_start);
}
static PedSector
legacy_end (const PedDisk* disk, const PedCHSGeometry* bios_geom,
const DosRawPartition* raw_part)
{
PED_ASSERT (disk != NULL, return 0);
PED_ASSERT (raw_part != NULL, return 0);
return chs_to_sector (disk->dev, bios_geom, &raw_part->chs_end);
}
static PedSector
linear_start (const PedDisk* disk, const DosRawPartition* raw_part,
PedSector offset)
{
PED_ASSERT (disk != NULL, return 0);
PED_ASSERT (raw_part != NULL, return 0);
return offset
+ PED_LE32_TO_CPU (raw_part->start)
* (disk->dev->sector_size / 512);
}
static PedSector
linear_end (const PedDisk* disk, const DosRawPartition* raw_part,
PedSector offset)
{
PED_ASSERT (disk != NULL, return 0);
PED_ASSERT (raw_part != NULL, return 0);
return linear_start (disk, raw_part, offset)
+ (PED_LE32_TO_CPU (raw_part->length) - 1)
* (disk->dev->sector_size / 512);
}
#ifndef DISCOVER_ONLY
static int
partition_check_bios_geometry (PedPartition* part, PedCHSGeometry* bios_geom)
{
PedSector leg_start, leg_end;
DosPartitionData* dos_data;
PedDisk* disk;
PED_ASSERT (part != NULL, return 0);
PED_ASSERT (part->disk != NULL, return 0);
PED_ASSERT (part->disk_specific != NULL, return 0);
dos_data = part->disk_specific;
if (!dos_data->orig)
return 1;
disk = part->disk;
leg_start = legacy_start (disk, bios_geom, &dos_data->orig->raw_part);
leg_end = legacy_end (disk, bios_geom, &dos_data->orig->raw_part);
if (leg_start && leg_start != dos_data->orig->geom.start)
return 0;
if (leg_end && leg_end != dos_data->orig->geom.end)
return 0;
return 1;
}
static int
disk_check_bios_geometry (const PedDisk* disk, PedCHSGeometry* bios_geom)
{
PedPartition* part = NULL;
PED_ASSERT (disk != NULL, return 0);
while ((part = ped_disk_next_partition (disk, part))) {
if (ped_partition_is_active (part)) {
if (!partition_check_bios_geometry (part, bios_geom))
return 0;
}
}
return 1;
}
static int
probe_filesystem_for_geom (const PedPartition* part, PedCHSGeometry* bios_geom)
{
const char* ms_types[] = {"ntfs", "fat16", "fat32", NULL};
int i;
int found;
unsigned char* buf;
int sectors;
int heads;
int res = 0;
PED_ASSERT (bios_geom != NULL, return 0);
PED_ASSERT (part != NULL, return 0);
PED_ASSERT (part->disk != NULL, return 0);
PED_ASSERT (part->disk->dev != NULL, return 0);
PED_ASSERT (part->disk->dev->sector_size % PED_SECTOR_SIZE_DEFAULT == 0,
return 0);
buf = ped_malloc (part->disk->dev->sector_size);
if (!buf)
return 0;
if (!part->fs_type)
goto end;
found = 0;
for (i = 0; ms_types[i]; i++) {
if (!strcmp(ms_types[i], part->fs_type->name))
found = 1;
}
if (!found)
goto end;
if (!ped_geometry_read(&part->geom, buf, 0, 1))
goto end;
/* shared by the start of all Microsoft file systems */
sectors = buf[0x18] + (buf[0x19] << 8);
heads = buf[0x1a] + (buf[0x1b] << 8);
if (sectors < 1 || sectors > 63)
goto end;
if (heads > 255 || heads < 1)
goto end;
bios_geom->sectors = sectors;
bios_geom->heads = heads;
bios_geom->cylinders = part->disk->dev->length / (sectors * heads);
res = 1;
end:
ped_free(buf);
return res;
}
/* This function attempts to infer the BIOS CHS geometry of the hard disk
* from the CHS + LBA information contained in the partition table from
* a single partition's entry.
*
* This involves some maths. Let (c,h,s,a) be the starting cylinder,
* starting head, starting sector and LBA start address of the partition.
* Likewise, (C,H,S,A) the end addresses. Using both of these pieces
* of information, we want to deduce cyl_sectors and head_sectors which
* are the sizes of a single cylinder and a single head, respectively.
*
* The relationships are:
* c*cyl_sectors + h * head_sectors + s = a
* C*cyl_sectors + H * head_sectors + S = A
*
* We can rewrite this in matrix form:
*
* [ c h ] [ cyl_sectors ] = [ s - a ] = [ a_ ]
* [ C H ] [ head_sectors ] [ S - A ] [ A_ ].
*
* (s - a is abbreviated to a_to simplify the notation.)
*
* This can be abbreviated into augmented matrix form:
*
* [ c h | a_ ]
* [ C H | A_ ].
*
* Solving these equations requires following the row reduction algorithm. We
* need to be careful about a few things though:
* - the equations might be linearly dependent, in which case there
* are many solutions.
* - the equations might be inconsistent, in which case there
* are no solutions. (Inconsistent partition table entry!)
* - there might be zeros, so we need to be careful about applying
* the algorithm. We know, however, that C > 0.
*/
static int
probe_partition_for_geom (const PedPartition* part, PedCHSGeometry* bios_geom)
{
DosPartitionData* dos_data;
RawCHS* start_chs;
RawCHS* end_chs;
PedSector c, h, s, a, a_; /* start */
PedSector C, H, S, A, A_; /* end */
PedSector dont_overflow, denum;
PedSector cyl_size, head_size;
PedSector cylinders, heads, sectors;
PED_ASSERT (part != NULL, return 0);
PED_ASSERT (part->disk_specific != NULL, return 0);
PED_ASSERT (bios_geom != NULL, return 0);
dos_data = part->disk_specific;
if (!dos_data->orig)
return 0;
start_chs = &dos_data->orig->raw_part.chs_start;
c = chs_get_cylinder (start_chs);
h = chs_get_head (start_chs);
s = chs_get_sector (start_chs);
a = dos_data->orig->geom.start;
a_ = a - s;
end_chs = &dos_data->orig->raw_part.chs_end;
C = chs_get_cylinder (end_chs);
H = chs_get_head (end_chs);
S = chs_get_sector (end_chs);
A = dos_data->orig->geom.end;
A_ = A - S;
if (h < 0 || H < 0 || h > 254 || H > 254)
return 0;
if (c > C)
return 0;
/* If no geometry is feasible, then don't even bother.
* Useful for eliminating assertions for broken partition
* tables generated by Norton Ghost et al.
*/
if (A > (C+1) * 255 * 63)
return 0;
/* Not enough information. In theory, we can do better. Should we? */
if (C > MAX_CHS_CYLINDER)
return 0;
if (C == 0)
return 0;
/* Calculate the maximum number that can be multiplied by
* any head count without overflowing a PedSector
* 2^8 = 256, 8 bits + 1(sign bit) = 9
*/
dont_overflow = 1;
dont_overflow <<= (8*sizeof(dont_overflow)) - 9;
dont_overflow--;
if (a_ > dont_overflow || A_ > dont_overflow)
return 0;
/* The matrix is solved by :
*
* [ c h | a_] R1
* [ C H | A_] R2
*
* (cH - Ch) cyl_size = a_H - A_h H R1 - h R2
* => (if cH - Ch != 0) cyl_size = (a_H - A_h) / (cH - Ch)
*
* (Hc - hC) head_size = A_c - a_C c R2 - C R1
* => (if cH - Ch != 0) head_size = (A_c - a_C) / (cH - Ch)
*
* But this calculation of head_size would need
* not overflowing A_c or a_C
* So substitution is use instead, to minimize dimension
* of temporary results :
*
* If h != 0 : head_size = ( a_ - c cyl_size ) / h
* If H != 0 : head_size = ( A_ - C cyl_size ) / H
*
*/
denum = c * H - C * h;
if (denum == 0)
return 0;
cyl_size = (a_*H - A_*h) / denum;
/* Check for non integer result */
if (cyl_size * denum != a_*H - A_*h)
return 0;
PED_ASSERT (cyl_size > 0, return 0);
PED_ASSERT (cyl_size <= 255 * 63, return 0);
if (h > 0)
head_size = ( a_ - c * cyl_size ) / h;
else if (H > 0)
head_size = ( A_ - C * cyl_size ) / H;
else {
/* should not happen because denum != 0 */
head_size = 0;
PED_ASSERT (0, return 0);
}
PED_ASSERT (head_size > 0, return 0);
PED_ASSERT (head_size <= 63, return 0);
cylinders = part->disk->dev->length / cyl_size;
heads = cyl_size / head_size;
sectors = head_size;
PED_ASSERT (heads > 0, return 0);
PED_ASSERT (heads < 256, return 0);
PED_ASSERT (sectors > 0, return 0);
PED_ASSERT (sectors <= 63, return 0);
/* Some broken OEM partitioning program(s) seem to have an out-by-one
* error on the end of partitions. We should offer to fix the
* partition table...
*/
if (((C + 1) * heads + H) * sectors + S == A)
C++;
PED_ASSERT ((c * heads + h) * sectors + s == a, return 0);
PED_ASSERT ((C * heads + H) * sectors + S == A, return 0);
bios_geom->cylinders = cylinders;
bios_geom->heads = heads;
bios_geom->sectors = sectors;
return 1;
}
static void
partition_probe_bios_geometry (const PedPartition* part,
PedCHSGeometry* bios_geom)
{
PED_ASSERT (part != NULL, return);
PED_ASSERT (part->disk != NULL, return);
PED_ASSERT (bios_geom != NULL, return);
if (ped_partition_is_active (part)) {
if (probe_partition_for_geom (part, bios_geom))
return;
if (part->type & PED_PARTITION_EXTENDED) {
if (probe_filesystem_for_geom (part, bios_geom))
return;
}
}
if (part->type & PED_PARTITION_LOGICAL) {
PedPartition* ext_part;
ext_part = ped_disk_extended_partition (part->disk);
PED_ASSERT (ext_part != NULL, return);
partition_probe_bios_geometry (ext_part, bios_geom);
} else {
*bios_geom = part->disk->dev->bios_geom;
}
}
static void
disk_probe_bios_geometry (const PedDisk* disk, PedCHSGeometry* bios_geom)
{
PedPartition* part;
/* first look at the boot partition */
part = NULL;
while ((part = ped_disk_next_partition (disk, part))) {
if (!ped_partition_is_active (part))
continue;
if (ped_partition_get_flag (part, PED_PARTITION_BOOT)) {
if (probe_filesystem_for_geom (part, bios_geom))
return;
if (probe_partition_for_geom (part, bios_geom))
return;
}
}
/* that didn't work... try all partition table entries */
part = NULL;
while ((part = ped_disk_next_partition (disk, part))) {
if (ped_partition_is_active (part)) {
if (probe_partition_for_geom (part, bios_geom))
return;
}
}
/* that didn't work... look at all file systems */
part = NULL;
while ((part = ped_disk_next_partition (disk, part))) {
if (ped_partition_is_active (part)) {
if (probe_filesystem_for_geom (part, bios_geom))
return;
}
}
}
#endif /* !DISCOVER_ONLY */
static int
raw_part_is_extended (const DosRawPartition* raw_part)
{
PED_ASSERT (raw_part != NULL, return 0);
switch (raw_part->type) {
case PARTITION_DOS_EXT:
case PARTITION_EXT_LBA:
case PARTITION_LINUX_EXT:
return 1;
default:
return 0;
}
return 0;
}
static int
raw_part_is_hidden (const DosRawPartition* raw_part)
{
PED_ASSERT (raw_part != NULL, return 0);
switch (raw_part->type) {
case PARTITION_FAT12_H:
case PARTITION_FAT16_SM_H:
case PARTITION_FAT16_H:
case PARTITION_FAT32_H:
case PARTITION_NTFS_H:
case PARTITION_FAT32_LBA_H:
case PARTITION_FAT16_LBA_H:
return 1;
default:
return 0;
}
return 0;
}
static int
raw_part_is_lba (const DosRawPartition* raw_part)
{
PED_ASSERT (raw_part != NULL, return 0);
switch (raw_part->type) {
case PARTITION_FAT32_LBA:
case PARTITION_FAT16_LBA:
case PARTITION_EXT_LBA:
case PARTITION_FAT32_LBA_H:
case PARTITION_FAT16_LBA_H:
return 1;
default:
return 0;
}
return 0;
}
static PedPartition*
raw_part_parse (const PedDisk* disk, const DosRawPartition* raw_part,
PedSector lba_offset, PedPartitionType type)
{
PedPartition* part;
DosPartitionData* dos_data;
PED_ASSERT (disk != NULL, return NULL);
PED_ASSERT (raw_part != NULL, return NULL);
part = ped_partition_new (
disk, type, NULL,
linear_start (disk, raw_part, lba_offset),
linear_end (disk, raw_part, lba_offset));
if (!part)
return NULL;
dos_data = part->disk_specific;
dos_data->system = raw_part->type;
dos_data->boot = raw_part->boot_ind != 0;
dos_data->hidden = raw_part_is_hidden (raw_part);
dos_data->raid = raw_part->type == PARTITION_LINUX_RAID;
dos_data->lvm = raw_part->type == PARTITION_LINUX_LVM_OLD
|| raw_part->type == PARTITION_LINUX_LVM;
dos_data->lba = raw_part_is_lba (raw_part);
dos_data->palo = raw_part->type == PARTITION_PALO;
dos_data->prep = raw_part->type == PARTITION_PREP;
dos_data->orig = ped_malloc (sizeof (OrigState));
if (!dos_data->orig) {
ped_partition_destroy (part);
return NULL;
}
dos_data->orig->geom = part->geom;
dos_data->orig->raw_part = *raw_part;
dos_data->orig->lba_offset = lba_offset;
return part;
}
static int
read_table (PedDisk* disk, PedSector sector, int is_extended_table)
{
int i;
DosRawTable table;
DosRawPartition* raw_part;
PedPartition* part;
PedPartitionType type;
PedSector lba_offset;
PedConstraint* constraint_exact;
PED_ASSERT (disk != NULL, return 0);
PED_ASSERT (disk->dev != NULL, return 0);
if (!ped_device_read (disk->dev, (void*) &table, sector, 1))
goto error;
/* weird: empty extended partitions are filled with 0xf6 by PM */
if (is_extended_table
&& PED_LE16_TO_CPU (table.magic) == PARTITION_MAGIC_MAGIC)
return 1;
#ifndef DISCOVER_ONLY
if (PED_LE16_TO_CPU (table.magic) != MSDOS_MAGIC) {
if (ped_exception_throw (
PED_EXCEPTION_ERROR, PED_EXCEPTION_IGNORE_CANCEL,
_("Invalid partition table on %s "
"-- wrong signature %x."),
disk->dev->path,
PED_LE16_TO_CPU (table.magic))
!= PED_EXCEPTION_IGNORE)
goto error;
return 1;
}
#endif
/* parse the partitions from this table */
for (i = 0; i < 4; i++) {
raw_part = &table.partitions [i];
if (raw_part->type == PARTITION_EMPTY || !raw_part->length)
continue;
/* process nested extended partitions after normal logical
* partitions, to make sure we get the order right.
*/
if (is_extended_table && raw_part_is_extended (raw_part))
continue;
lba_offset = is_extended_table ? sector : 0;
if (linear_start (disk, raw_part, lba_offset) == sector) {
if (ped_exception_throw (
PED_EXCEPTION_ERROR,
PED_EXCEPTION_IGNORE_CANCEL,
_("Invalid partition table - recursive "
"partition on %s."),
disk->dev->path)
!= PED_EXCEPTION_IGNORE)
goto error;
continue; /* avoid infinite recursion */
}
if (is_extended_table)
type = PED_PARTITION_LOGICAL;
else if (raw_part_is_extended (raw_part))
type = PED_PARTITION_EXTENDED;
else
type = PED_PARTITION_NORMAL;
part = raw_part_parse (disk, raw_part, lba_offset, type);
if (!part)
goto error;
if (!is_extended_table)
part->num = i + 1;
if (type != PED_PARTITION_EXTENDED)
part->fs_type = ped_file_system_probe (&part->geom);
constraint_exact = ped_constraint_exact (&part->geom);
if (!ped_disk_add_partition (disk, part, constraint_exact))
goto error;
ped_constraint_destroy (constraint_exact);
/* non-nested extended partition */
if (part->type == PED_PARTITION_EXTENDED) {
if (!read_table (disk, part->geom.start, 1))
goto error;
}
}
if (is_extended_table) {
/* process the nested extended partitions */
for (i = 0; i < 4; i++) {
PedSector part_start;
raw_part = &table.partitions [i];
if (!raw_part_is_extended (raw_part))
continue;
lba_offset = ped_disk_extended_partition
(disk)->geom.start;
part_start = linear_start (disk, raw_part, lba_offset);
if (part_start == sector) {
/* recursive table - already threw an
* exception above.
*/
continue;
}
if (!read_table (disk, part_start, 1))
goto error;
}
}
return 1;
error:
ped_disk_delete_all (disk);
return 0;
}
static int
msdos_read (PedDisk* disk)
{
PED_ASSERT (disk != NULL, return 0);
PED_ASSERT (disk->dev != NULL, return 0);
ped_disk_delete_all (disk);
if (!read_table (disk, 0, 0))
return 0;
#ifndef DISCOVER_ONLY
/* try to figure out the correct BIOS CHS values */
if (!disk_check_bios_geometry (disk, &disk->dev->bios_geom)) {
PedCHSGeometry bios_geom = disk->dev->bios_geom;
disk_probe_bios_geometry (disk, &bios_geom);
/* if the geometry was wrong, then we should reread, to
* make sure the metadata is allocated in the right places.
*/
if (disk->dev->bios_geom.cylinders != bios_geom.cylinders
|| disk->dev->bios_geom.heads != bios_geom.heads
|| disk->dev->bios_geom.sectors != bios_geom.sectors) {
disk->dev->bios_geom = bios_geom;
return msdos_read (disk);
}
}
#endif
return 1;
}
#ifndef DISCOVER_ONLY
static int
fill_raw_part (DosRawPartition* raw_part,
const PedPartition* part, PedSector offset)
{
DosPartitionData* dos_data;
PedCHSGeometry bios_geom;
PED_ASSERT (raw_part != NULL, return 0);
PED_ASSERT (part != NULL, return 0);
partition_probe_bios_geometry (part, &bios_geom);
dos_data = part->disk_specific;
raw_part->boot_ind = 0x80 * dos_data->boot;
raw_part->type = dos_data->system;
raw_part->start = PED_CPU_TO_LE32 ((part->geom.start - offset)
/ (part->disk->dev->sector_size / 512));
raw_part->length = PED_CPU_TO_LE32 (part->geom.length
/ (part->disk->dev->sector_size / 512));
sector_to_chs (part->disk->dev, &bios_geom, part->geom.start,
&raw_part->chs_start);
sector_to_chs (part->disk->dev, &bios_geom, part->geom.end,
&raw_part->chs_end);
if (dos_data->orig) {
DosRawPartition* orig_raw_part = &dos_data->orig->raw_part;
if (dos_data->orig->geom.start == part->geom.start)
raw_part->chs_start = orig_raw_part->chs_start;
if (dos_data->orig->geom.end == part->geom.end)
raw_part->chs_end = orig_raw_part->chs_end;
}
return 1;
}
static int
fill_ext_raw_part_geom (DosRawPartition* raw_part,
const PedCHSGeometry* bios_geom,
const PedGeometry* geom, PedSector offset)
{
PED_ASSERT (raw_part != NULL, return 0);
PED_ASSERT (geom != NULL, return 0);
PED_ASSERT (geom->dev != NULL, return 0);
raw_part->boot_ind = 0;
raw_part->type = PARTITION_DOS_EXT;
raw_part->start = PED_CPU_TO_LE32 ((geom->start - offset)
/ (geom->dev->sector_size / 512));
raw_part->length = PED_CPU_TO_LE32 (geom->length
/ (geom->dev->sector_size / 512));
sector_to_chs (geom->dev, bios_geom, geom->start, &raw_part->chs_start);
sector_to_chs (geom->dev, bios_geom, geom->start + geom->length - 1,
&raw_part->chs_end);
return 1;
}
static int
write_ext_table (const PedDisk* disk,
PedSector sector, const PedPartition* logical)
{
DosRawTable table;
PedPartition* part;
PedSector lba_offset;
PED_ASSERT (disk != NULL, return 0);
PED_ASSERT (ped_disk_extended_partition (disk) != NULL, return 0);
PED_ASSERT (logical != NULL, return 0);
lba_offset = ped_disk_extended_partition (disk)->geom.start;
memset (&table, 0, sizeof (DosRawTable));
table.magic = PED_CPU_TO_LE16 (MSDOS_MAGIC);
if (!fill_raw_part (&table.partitions[0], logical, sector))
return 0;
part = ped_disk_get_partition (disk, logical->num + 1);
if (part) {
PedGeometry* geom;
PedCHSGeometry bios_geom;
geom = ped_geometry_new (disk->dev, part->prev->geom.start,
part->geom.end - part->prev->geom.start + 1);
if (!geom)
return 0;
partition_probe_bios_geometry (part, &bios_geom);
fill_ext_raw_part_geom (&table.partitions[1], &bios_geom,
geom, lba_offset);
ped_geometry_destroy (geom);
if (!write_ext_table (disk, part->prev->geom.start, part))
return 0;
}
return ped_device_write (disk->dev, (void*) &table, sector, 1);
}
static int
write_empty_table (const PedDisk* disk, PedSector sector)
{
DosRawTable table;
PED_ASSERT (disk != NULL, return 0);
memset (&table, 0, sizeof (DosRawTable));
table.magic = PED_CPU_TO_LE16 (MSDOS_MAGIC);
return ped_device_write (disk->dev, (void*) &table, sector, 1);
}
/* Find the first logical partition, and write the partition table for it.
*/
static int
write_extended_partitions (const PedDisk* disk)
{
PedPartition* ext_part;
PedPartition* part;
PedCHSGeometry bios_geom;
PED_ASSERT (disk != NULL, return 0);
ext_part = ped_disk_extended_partition (disk);
partition_probe_bios_geometry (ext_part, &bios_geom);
part = ped_disk_get_partition (disk, 5);
if (part)
return write_ext_table (disk, ext_part->geom.start, part);
else
return write_empty_table (disk, ext_part->geom.start);
}
static inline uint32_t generate_random_id (void)
{
struct timeval tv;
int rc;
rc = gettimeofday(&tv, NULL);
if (rc == -1)
return 0;
return (uint32_t)(tv.tv_usec & 0xFFFFFFFFUL);
}
static int
msdos_write (const PedDisk* disk)
{
DosRawTable table;
PedPartition* part;
int i;
PED_ASSERT (disk != NULL, return 0);
PED_ASSERT (disk->dev != NULL, return 0);
ped_device_read (disk->dev, &table, 0, 1);
if (!table.boot_code[0]) {
memset (table.boot_code, 0, 512);
memcpy (table.boot_code, MBR_BOOT_CODE, sizeof (MBR_BOOT_CODE));
}
/* If there is no unique identifier, generate a random one */
if (!table.mbr_signature)
table.mbr_signature = generate_random_id();
memset (table.partitions, 0, sizeof (DosRawPartition) * 4);
table.magic = PED_CPU_TO_LE16 (MSDOS_MAGIC);
for (i=1; i<=4; i++) {
part = ped_disk_get_partition (disk, i);
if (!part)
continue;
if (!fill_raw_part (&table.partitions [i - 1], part, 0))
return 0;
if (part->type == PED_PARTITION_EXTENDED) {
if (!write_extended_partitions (disk))
return 0;
}
}
if (!ped_device_write (disk->dev, (void*) &table, 0, 1))
return 0;
return ped_device_sync (disk->dev);
}
#endif /* !DISCOVER_ONLY */
static PedPartition*
msdos_partition_new (const PedDisk* disk, PedPartitionType part_type,
const PedFileSystemType* fs_type,
PedSector start, PedSector end)
{
PedPartition* part;
DosPartitionData* dos_data;
part = _ped_partition_alloc (disk, part_type, fs_type, start, end);
if (!part)
goto error;
if (ped_partition_is_active (part)) {
part->disk_specific
= dos_data = ped_malloc (sizeof (DosPartitionData));
if (!dos_data)
goto error_free_part;
dos_data->orig = NULL;
dos_data->system = PARTITION_LINUX;
dos_data->hidden = 0;
dos_data->boot = 0;
dos_data->raid = 0;
dos_data->lvm = 0;
dos_data->lba = 0;
dos_data->palo = 0;
dos_data->prep = 0;
} else {
part->disk_specific = NULL;
}
return part;
ped_free (dos_data);
error_free_part:
ped_free (part);
error:
return 0;
}
static PedPartition*
msdos_partition_duplicate (const PedPartition* part)
{
PedPartition* new_part;
DosPartitionData* new_dos_data;
DosPartitionData* old_dos_data;
new_part = ped_partition_new (part->disk, part->type, part->fs_type,
part->geom.start, part->geom.end);
if (!new_part)
return NULL;
new_part->num = part->num;
old_dos_data = (DosPartitionData*) part->disk_specific;
new_dos_data = (DosPartitionData*) new_part->disk_specific;
new_dos_data->system = old_dos_data->system;
new_dos_data->boot = old_dos_data->boot;
new_dos_data->hidden = old_dos_data->hidden;
new_dos_data->raid = old_dos_data->raid;
new_dos_data->lvm = old_dos_data->lvm;
new_dos_data->lba = old_dos_data->lba;
new_dos_data->palo = old_dos_data->palo;
new_dos_data->prep = old_dos_data->prep;
if (old_dos_data->orig) {
new_dos_data->orig = ped_malloc (sizeof (OrigState));
if (!new_dos_data->orig) {
ped_partition_destroy (new_part);
return NULL;
}
new_dos_data->orig->geom = old_dos_data->orig->geom;
new_dos_data->orig->raw_part = old_dos_data->orig->raw_part;
new_dos_data->orig->lba_offset = old_dos_data->orig->lba_offset;
}
return new_part;
}
static void
msdos_partition_destroy (PedPartition* part)
{
PED_ASSERT (part != NULL, return);
if (ped_partition_is_active (part)) {
DosPartitionData* dos_data;
dos_data = (DosPartitionData*) part->disk_specific;
if (dos_data->orig)
ped_free (dos_data->orig);
ped_free (part->disk_specific);
}
ped_free (part);
}
static int
msdos_partition_set_system (PedPartition* part,
const PedFileSystemType* fs_type)
{
DosPartitionData* dos_data = part->disk_specific;
part->fs_type = fs_type;
if (dos_data->hidden
&& fs_type
&& strncmp (fs_type->name, "fat", 3) != 0
&& strcmp (fs_type->name, "ntfs") != 0)
dos_data->hidden = 0;
if (part->type & PED_PARTITION_EXTENDED) {
dos_data->raid = 0;
dos_data->lvm = 0;
dos_data->palo = 0;
dos_data->prep = 0;
if (dos_data->lba)
dos_data->system = PARTITION_EXT_LBA;
else
dos_data->system = PARTITION_DOS_EXT;
return 1;
}
if (dos_data->lvm) {
dos_data->system = PARTITION_LINUX_LVM;
return 1;
}
if (dos_data->raid) {
dos_data->system = PARTITION_LINUX_RAID;
return 1;
}
if (dos_data->palo) {
dos_data->system = PARTITION_PALO;
return 1;
}
if (dos_data->prep) {
dos_data->system = PARTITION_PREP;
return 1;
}
if (!fs_type)
dos_data->system = PARTITION_LINUX;
else if (!strcmp (fs_type->name, "fat16")) {
dos_data->system = dos_data->lba
? PARTITION_FAT16_LBA : PARTITION_FAT16;
dos_data->system |= dos_data->hidden ? PART_FLAG_HIDDEN : 0;
} else if (!strcmp (fs_type->name, "fat32")) {
dos_data->system = dos_data->lba
? PARTITION_FAT32_LBA : PARTITION_FAT32;
dos_data->system |= dos_data->hidden ? PART_FLAG_HIDDEN : 0;
} else if (!strcmp (fs_type->name, "ntfs")
|| !strcmp (fs_type->name, "hpfs")) {
dos_data->system = PARTITION_NTFS;
dos_data->system |= dos_data->hidden ? PART_FLAG_HIDDEN : 0;
} else if (!strcmp (fs_type->name, "sun-ufs"))
dos_data->system = PARTITION_SUN_UFS;
else if (!strcmp (fs_type->name, "linux-swap"))
dos_data->system = PARTITION_LINUX_SWAP;
else
dos_data->system = PARTITION_LINUX;
return 1;
}
static int
msdos_partition_set_flag (PedPartition* part,
PedPartitionFlag flag, int state)
{
PedDisk* disk;
PedPartition* walk;
DosPartitionData* dos_data;
PED_ASSERT (part != NULL, return 0);
PED_ASSERT (part->disk_specific != NULL, return 0);
PED_ASSERT (part->disk != NULL, return 0);
dos_data = part->disk_specific;
disk = part->disk;
switch (flag) {
case PED_PARTITION_HIDDEN:
if (part->type == PED_PARTITION_EXTENDED) {
ped_exception_throw (
PED_EXCEPTION_ERROR,
PED_EXCEPTION_CANCEL,
_("Extended partitions cannot be hidden on "
"msdos disk labels."));
return 0;
}
dos_data->hidden = state;
return ped_partition_set_system (part, part->fs_type);
case PED_PARTITION_BOOT:
dos_data->boot = state;
if (!state)
return 1;
walk = ped_disk_next_partition (disk, NULL);
for (; walk; walk = ped_disk_next_partition (disk, walk)) {
if (walk == part || !ped_partition_is_active (walk))
continue;
msdos_partition_set_flag (walk, PED_PARTITION_BOOT, 0);
}
return 1;
case PED_PARTITION_RAID:
if (state) {
dos_data->hidden = 0;
dos_data->lvm = 0;
dos_data->palo = 0;
dos_data->prep = 0;
}
dos_data->raid = state;
return ped_partition_set_system (part, part->fs_type);
case PED_PARTITION_LVM:
if (state) {
dos_data->hidden = 0;
dos_data->raid = 0;
dos_data->palo = 0;
dos_data->prep = 0;
}
dos_data->lvm = state;
return ped_partition_set_system (part, part->fs_type);
case PED_PARTITION_LBA:
dos_data->lba = state;
return ped_partition_set_system (part, part->fs_type);
case PED_PARTITION_PALO:
if (state) {
dos_data->hidden = 0;
dos_data->raid = 0;
dos_data->lvm = 0;
}
dos_data->palo = state;
return ped_partition_set_system (part, part->fs_type);
case PED_PARTITION_PREP:
if (state) {
dos_data->hidden = 0;
dos_data->raid = 0;
dos_data->lvm = 0;
}
dos_data->prep = state;
return ped_partition_set_system (part, part->fs_type);
default:
return 0;
}
}
static int
msdos_partition_get_flag (const PedPartition* part, PedPartitionFlag flag)
{
DosPartitionData* dos_data;
PED_ASSERT (part != NULL, return 0);
PED_ASSERT (part->disk_specific != NULL, return 0);
dos_data = part->disk_specific;
switch (flag) {
case PED_PARTITION_HIDDEN:
return dos_data->hidden;
case PED_PARTITION_BOOT:
return dos_data->boot;
case PED_PARTITION_RAID:
return dos_data->raid;
case PED_PARTITION_LVM:
return dos_data->lvm;
case PED_PARTITION_LBA:
return dos_data->lba;
case PED_PARTITION_PALO:
return dos_data->palo;
case PED_PARTITION_PREP:
return dos_data->prep;
default:
return 0;
}
}
static int
msdos_partition_is_flag_available (const PedPartition* part,
PedPartitionFlag flag)
{
switch (flag) {
case PED_PARTITION_HIDDEN:
case PED_PARTITION_BOOT:
case PED_PARTITION_RAID:
case PED_PARTITION_LVM:
case PED_PARTITION_LBA:
case PED_PARTITION_PALO:
case PED_PARTITION_PREP:
return 1;
default:
return 0;
}
}
static PedGeometry*
_try_constraint (const PedPartition* part, const PedConstraint* external,
PedConstraint* internal)
{
PedConstraint* intersection;
PedGeometry* solution;
intersection = ped_constraint_intersect (external, internal);
ped_constraint_destroy (internal);
if (!intersection)
return NULL;
solution = ped_constraint_solve_nearest (intersection, &part->geom);
ped_constraint_destroy (intersection);
return solution;
}
static PedGeometry*
_best_solution (const PedPartition* part, const PedCHSGeometry* bios_geom,
PedGeometry* a, PedGeometry* b)
{
PedSector cyl_size = bios_geom->heads * bios_geom->sectors;
int a_cylinder;
int b_cylinder;
if (!a)
return b;
if (!b)
return a;
a_cylinder = a->start / cyl_size;
b_cylinder = b->start / cyl_size;
if (a_cylinder == b_cylinder) {
if ( (a->start / bios_geom->sectors) % bios_geom->heads
< (b->start / bios_geom->sectors) % bios_geom->heads)
goto choose_a;
else
goto choose_b;
} else {
PedSector a_delta;
PedSector b_delta;
a_delta = abs (part->geom.start - a->start);
b_delta = abs (part->geom.start - b->start);
if (a_delta < b_delta)
goto choose_a;
else
goto choose_b;
}
return NULL; /* never get here! */
choose_a:
ped_geometry_destroy (b);
return a;
choose_b:
ped_geometry_destroy (a);
return b;
}
/* This constraint is for "normal" primary partitions, that start at the
* beginning of a cylinder, and end at the end of a cylinder.
* Note: you can't start a partition at the beginning of the 1st
* cylinder, because that's where the partition table is! There are different
* rules for that - see the _primary_start_constraint.
*/
static PedConstraint*
_primary_constraint (const PedDisk* disk, const PedCHSGeometry* bios_geom,
PedGeometry* min_geom)
{
PedDevice* dev = disk->dev;
PedSector cylinder_size = bios_geom->sectors * bios_geom->heads;
PedAlignment start_align;
PedAlignment end_align;
PedGeometry start_geom;
PedGeometry end_geom;
if (!ped_alignment_init (&start_align, 0, cylinder_size))
return NULL;
if (!ped_alignment_init (&end_align, -1, cylinder_size))
return NULL;
if (min_geom) {
if (min_geom->start < cylinder_size)
return NULL;
if (!ped_geometry_init (&start_geom, dev, cylinder_size,
min_geom->start + 1 - cylinder_size))
return NULL;
if (!ped_geometry_init (&end_geom, dev, min_geom->end,
dev->length - min_geom->end))
return NULL;
} else {
if (!ped_geometry_init (&start_geom, dev, cylinder_size,
dev->length - cylinder_size))
return NULL;
if (!ped_geometry_init (&end_geom, dev, 0, dev->length))
return NULL;
}
return ped_constraint_new (&start_align, &end_align, &start_geom,
&end_geom, 1, dev->length);
}
/* This constraint is for partitions starting on the first cylinder. They
* must start on the 2nd head of the 1st cylinder.
*
* NOTE: We don't always start on the 2nd head of the 1st cylinder. Windows
* Vista aligns starting partitions at sector 2048 (0x800) by default. See:
* http://support.microsoft.com/kb/923332
*/
static PedConstraint*
_primary_start_constraint (const PedDisk* disk,
const PedPartition *part,
const PedCHSGeometry* bios_geom,
const PedGeometry* min_geom)
{
PedDevice* dev = disk->dev;
PedSector cylinder_size = bios_geom->sectors * bios_geom->heads;
PedAlignment start_align;
PedAlignment end_align;
PedGeometry start_geom;
PedGeometry end_geom;
PedSector start_pos;
if (part->geom.start == 2048)
/* check for known Windows Vista (NTFS >= 3.1) alignments */
/* sector 0x800 == 2048 */
start_pos = 2048;
else
/* all other primary partitions on a DOS label align to */
/* the 2nd head of the first cylinder (0x3F == 63) */
start_pos = bios_geom->sectors;
if (!ped_alignment_init (&start_align, start_pos, 0))
return NULL;
if (!ped_alignment_init (&end_align, -1, cylinder_size))
return NULL;
if (min_geom) {
if (!ped_geometry_init (&start_geom, dev, start_pos, 1))
return NULL;
if (!ped_geometry_init (&end_geom, dev, min_geom->end,
dev->length - min_geom->end))
return NULL;
} else {
if (!ped_geometry_init (&start_geom, dev, start_pos,
dev->length - start_pos))
return NULL;
if (!ped_geometry_init (&end_geom, dev, 0, dev->length))
return NULL;
}
return ped_constraint_new (&start_align, &end_align, &start_geom,
&end_geom, 1, dev->length);
}
/* constraints for logical partitions:
* - start_offset is the offset in the start alignment. "normally",
* this is bios_geom->sectors. exceptions: MINOR > 5 at the beginning of the
* extended partition, or MINOR == 5 in the middle of the extended partition
* - is_start_part == 1 if the constraint is for the first cylinder of
* the extended partition, or == 0 if the constraint is for the second cylinder
* onwards of the extended partition.
*/
static PedConstraint*
_logical_constraint (const PedDisk* disk, const PedCHSGeometry* bios_geom,
PedSector start_offset, int is_start_part)
{
PedPartition* ext_part = ped_disk_extended_partition (disk);
PedDevice* dev = disk->dev;
PedSector cylinder_size = bios_geom->sectors * bios_geom->heads;
PedAlignment start_align;
PedAlignment end_align;
PedGeometry max_geom;
PED_ASSERT (ext_part != NULL, return NULL);
if (!ped_alignment_init (&start_align, start_offset, cylinder_size))
return NULL;
if (!ped_alignment_init (&end_align, -1, cylinder_size))
return NULL;
if (is_start_part) {
if (!ped_geometry_init (&max_geom, dev,
ext_part->geom.start,
ext_part->geom.length))
return NULL;
} else {
PedSector min_start;
PedSector max_length;
min_start = ped_round_up_to (ext_part->geom.start + 1,
cylinder_size);
max_length = ext_part->geom.end - min_start + 1;
if (min_start >= ext_part->geom.end)
return NULL;
if (!ped_geometry_init (&max_geom, dev, min_start, max_length))
return NULL;
}
return ped_constraint_new (&start_align, &end_align, &max_geom,
&max_geom, 1, dev->length);
}
/* returns the minimum geometry for the extended partition, given that the
* extended partition must contain:
* * all logical partitions
* * all partition tables for all logical partitions (except the first)
* * the extended partition table
*/
static PedGeometry*
_get_min_extended_part_geom (const PedPartition* ext_part,
const PedCHSGeometry* bios_geom)
{
PedDisk* disk = ext_part->disk;
PedSector head_size = bios_geom ? bios_geom->sectors : 1;
PedPartition* walk;
PedGeometry* min_geom;
walk = ped_disk_get_partition (disk, 5);
if (!walk)
return NULL;
min_geom = ped_geometry_duplicate (&walk->geom);
if (!min_geom)
return NULL;
ped_geometry_set_start (min_geom, walk->geom.start - 1 * head_size);
for (walk = ext_part->part_list; walk; walk = walk->next) {
if (!ped_partition_is_active (walk) || walk->num == 5)
continue;
if (walk->geom.start < min_geom->start)
ped_geometry_set_start (min_geom,
walk->geom.start - 2 * head_size);
if (walk->geom.end > min_geom->end)
ped_geometry_set_end (min_geom, walk->geom.end);
}
return min_geom;
}
static int
_align_primary (PedPartition* part, const PedCHSGeometry* bios_geom,
const PedConstraint* constraint)
{
PedDisk* disk = part->disk;
PedGeometry* min_geom = NULL;
PedGeometry* solution = NULL;
if (part->type == PED_PARTITION_EXTENDED)
min_geom = _get_min_extended_part_geom (part, bios_geom);
solution = _best_solution (part, bios_geom, solution,
_try_constraint (part, constraint,
_primary_start_constraint (disk, part,
bios_geom, min_geom)));
solution = _best_solution (part, bios_geom, solution,
_try_constraint (part, constraint,
_primary_constraint (disk, bios_geom,
min_geom)));
if (min_geom)
ped_geometry_destroy (min_geom);
if (solution) {
ped_geometry_set (&part->geom, solution->start,
solution->length);
ped_geometry_destroy (solution);
return 1;
}
return 0;
}
static int
_logical_min_start_head (const PedPartition* part,
const PedCHSGeometry* bios_geom,
const PedPartition* ext_part,
int is_start_ext_part)
{
PedSector cylinder_size = bios_geom->sectors * bios_geom->heads;
PedSector base_head;
if (is_start_ext_part)
base_head = 1 + (ext_part->geom.start % cylinder_size)
/ bios_geom->sectors;
else
base_head = 0;
if (part->num == 5)
return base_head + 0;
else
return base_head + 1;
}
/* Shamelessly copied and adapted from _partition_get_overlap_constraint
* (in disk.c)
* This should get ride of the infamous Assertion (metadata_length > 0) failed
* bug for extended msdos disklabels generated by Parted.
* 1) There always is a partition table at the start of ext_part, so we leave
* a one sector gap there.
* 2)*The partition table of part5 is always at the beginning of the ext_part
* so there is no need to leave a one sector gap before part5.
* *There always is a partition table at the beginning of each partition != 5.
* We don't need to worry to much about consistency with
* _partition_get_overlap_constraint because missing it means we are in edge
* cases anyway, and we don't lose anything by just refusing to do the job in
* those cases.
*/
static PedConstraint*
_log_meta_overlap_constraint (PedPartition* part, const PedGeometry* geom)
{
PedGeometry safe_space;
PedSector min_start;
PedSector max_end;
PedPartition* ext_part = ped_disk_extended_partition (part->disk);
PedPartition* walk;
int not_5 = (part->num != 5);
PED_ASSERT (ext_part != NULL, return NULL);
walk = ext_part->part_list;
/* 1) 2) */
min_start = ext_part->geom.start + 1 + not_5;
max_end = ext_part->geom.end;
while (walk != NULL /* 2) 2) */
&& ( walk->geom.start - (walk->num != 5) < geom->start - not_5
|| walk->geom.start - (walk->num != 5) <= min_start )) {
if (walk != part && ped_partition_is_active (walk))
min_start = walk->geom.end + 1 + not_5; /* 2) */
walk = walk->next;
}
while (walk && (walk == part || !ped_partition_is_active (walk)))
walk = walk->next;
if (walk)
max_end = walk->geom.start - 1 - (walk->num != 5); /* 2) */
if (min_start >= max_end)
return NULL;
ped_geometry_init (&safe_space, part->disk->dev,
min_start, max_end - min_start + 1);
return ped_constraint_new_from_max (&safe_space);
}
static int
_align_logical (PedPartition* part, const PedCHSGeometry* bios_geom,
const PedConstraint* constraint)
{
PedDisk* disk = part->disk;
PedPartition* ext_part = ped_disk_extended_partition (disk);
PedSector cyl_size = bios_geom->sectors * bios_geom->heads;
PedSector start_base;
int head;
PedGeometry* solution = NULL;
PedConstraint *intersect, *log_meta_overlap;
PED_ASSERT (ext_part != NULL, return 0);
log_meta_overlap = _log_meta_overlap_constraint(part, &part->geom);
intersect = ped_constraint_intersect (constraint, log_meta_overlap);
ped_constraint_destroy (log_meta_overlap);
if (!intersect)
return 0;
start_base = ped_round_down_to (part->geom.start, cyl_size);
for (head = _logical_min_start_head (part, bios_geom, ext_part, 0);
head < PED_MIN (5, bios_geom->heads); head++) {
PedConstraint* disk_constraint;
PedSector start = start_base + head * bios_geom->sectors;
if (head >= _logical_min_start_head (part, bios_geom,
ext_part, 1))
disk_constraint =
_logical_constraint (disk, bios_geom, start, 1);
else
disk_constraint =
_logical_constraint (disk, bios_geom, start, 0);
solution = _best_solution (part, bios_geom, solution,
_try_constraint (part, intersect,
disk_constraint));
}
ped_constraint_destroy (intersect);
if (solution) {
ped_geometry_set (&part->geom, solution->start,
solution->length);
ped_geometry_destroy (solution);
return 1;
}
return 0;
}
static int
_align (PedPartition* part, const PedCHSGeometry* bios_geom,
const PedConstraint* constraint)
{
if (part->type == PED_PARTITION_LOGICAL)
return _align_logical (part, bios_geom, constraint);
else
return _align_primary (part, bios_geom, constraint);
}
static PedConstraint*
_no_geom_constraint (const PedDisk* disk, PedSector start, PedSector end)
{
PedGeometry max;
ped_geometry_init (&max, disk->dev, start, end - start + 1);
return ped_constraint_new_from_max (&max);
}
static PedConstraint*
_no_geom_extended_constraint (const PedPartition* part)
{
PedDevice* dev = part->disk->dev;
PedGeometry* min = _get_min_extended_part_geom (part, NULL);
PedGeometry start_range;
PedGeometry end_range;
PedConstraint* constraint;
if (min) {
ped_geometry_init (&start_range, dev, 1, min->start);
ped_geometry_init (&end_range, dev, min->end,
dev->length - min->end);
ped_geometry_destroy (min);
} else {
ped_geometry_init (&start_range, dev, 1, dev->length - 1);
ped_geometry_init (&end_range, dev, 1, dev->length - 1);
}
constraint = ped_constraint_new (ped_alignment_any, ped_alignment_any,
&start_range, &end_range, 1, dev->length);
return constraint;
}
static int
_align_primary_no_geom (PedPartition* part, const PedConstraint* constraint)
{
PedDisk* disk = part->disk;
PedGeometry* solution;
if (part->type == PED_PARTITION_EXTENDED) {
solution = _try_constraint (part, constraint,
_no_geom_extended_constraint (part));
} else {
solution = _try_constraint (part, constraint,
_no_geom_constraint (disk, 1,
disk->dev->length - 1));
}
if (solution) {
ped_geometry_set (&part->geom, solution->start,
solution->length);
ped_geometry_destroy (solution);
return 1;
}
return 0;
}
static int
_align_logical_no_geom (PedPartition* part, const PedConstraint* constraint)
{
PedGeometry* solution;
solution = _try_constraint (part, constraint,
_log_meta_overlap_constraint (part, &part->geom));
if (solution) {
ped_geometry_set (&part->geom, solution->start,
solution->length);
ped_geometry_destroy (solution);
return 1;
}
return 0;
}
static int
_align_no_geom (PedPartition* part, const PedConstraint* constraint)
{
if (part->type == PED_PARTITION_LOGICAL)
return _align_logical_no_geom (part, constraint);
else
return _align_primary_no_geom (part, constraint);
}
static int
msdos_partition_align (PedPartition* part, const PedConstraint* constraint)
{
PedCHSGeometry bios_geom;
DosPartitionData* dos_data;
PED_ASSERT (part != NULL, return 0);
PED_ASSERT (part->disk_specific != NULL, return 0);
dos_data = part->disk_specific;
if (dos_data->system == PARTITION_LDM && dos_data->orig) {
PedGeometry *orig_geom = &dos_data->orig->geom;
if (ped_geometry_test_equal (&part->geom, orig_geom)
&& ped_constraint_is_solution (constraint, &part->geom))
return 1;
ped_geometry_set (&part->geom, orig_geom->start,
orig_geom->length);
ped_exception_throw (
PED_EXCEPTION_ERROR,
PED_EXCEPTION_CANCEL,
_("Parted can't resize partitions managed by "
"Windows Dynamic Disk."));
return 0;
}
partition_probe_bios_geometry (part, &bios_geom);
if (_align (part, &bios_geom, constraint))
return 1;
if (_align_no_geom (part, constraint))
return 1;
#ifndef DISCOVER_ONLY
ped_exception_throw (
PED_EXCEPTION_ERROR,
PED_EXCEPTION_CANCEL,
_("Unable to satisfy all constraints on the partition."));
#endif
return 0;
}
static int
add_metadata_part (PedDisk* disk, PedPartitionType type, PedSector start,
PedSector end)
{
PedPartition* new_part;
PED_ASSERT (disk != NULL, return 0);
new_part = ped_partition_new (disk, type | PED_PARTITION_METADATA, NULL,
start, end);
if (!new_part)
goto error;
if (!ped_disk_add_partition (disk, new_part, NULL))
goto error_destroy_new_part;
return 1;
error_destroy_new_part:
ped_partition_destroy (new_part);
error:
return 0;
}
/* There are a few objectives here:
* - avoid having lots of "free space" partitions lying around, to confuse
* the front end.
* - ensure that there's enough room to put in the extended partition
* tables, etc.
*/
static int
add_logical_part_metadata (PedDisk* disk, const PedPartition* log_part)
{
PedPartition* ext_part = ped_disk_extended_partition (disk);
PedPartition* prev = log_part->prev;
PedCHSGeometry bios_geom;
PedSector cyl_size;
PedSector metadata_start;
PedSector metadata_end;
PedSector metadata_length;
partition_probe_bios_geometry (ext_part, &bios_geom);
cyl_size = bios_geom.sectors * bios_geom.heads;
/* if there's metadata shortly before the partition (on the same
* cylinder), then make this new metadata partition touch the end of
* the other. No point having 63 bytes (or whatever) of free space
* partition - just confuses front-ends, etc.
* Otherwise, start the metadata at the start of the cylinder
*/
metadata_end = log_part->geom.start - 1;
metadata_start = ped_round_down_to (metadata_end, cyl_size);
if (prev)
metadata_start = PED_MAX (metadata_start, prev->geom.end + 1);
else
metadata_start = PED_MAX (metadata_start,
ext_part->geom.start + 1);
metadata_length = metadata_end - metadata_start + 1;
/* partition 5 doesn't need to have any metadata */
if (log_part->num == 5 && metadata_length < bios_geom.sectors)
return 1;
PED_ASSERT (metadata_length > 0, return 0);
return add_metadata_part (disk, PED_PARTITION_LOGICAL,
metadata_start, metadata_end);
}
static PedPartition*
get_last_part (const PedDisk* disk)
{
PedPartition* first_part = disk->part_list;
PedPartition* walk;
if (!first_part)
return NULL;
for (walk = first_part; walk->next; walk = walk->next);
return walk;
}
/* Adds metadata placeholder partitions to cover the partition table (and
* "free" space after it that often has bootloader stuff), and the last
* incomplete cylinder at the end of the disk.
* Parted has to be mindful of the uncertainty of dev->bios_geom.
* It therefore makes sure this metadata doesn't overlap with partitions.
*/
static int
add_startend_metadata (PedDisk* disk)
{
PedDevice* dev = disk->dev;
PedSector cyl_size = dev->bios_geom.sectors * dev->bios_geom.heads;
PedPartition* first_part = disk->part_list;
PedPartition* last_part = get_last_part (disk);
PedSector start, end;
if (!first_part)
return 1;
start = 0;
end = PED_MIN (dev->bios_geom.sectors - 1, first_part->geom.start - 1);
if (!add_metadata_part (disk, PED_PARTITION_NORMAL, start, end))
return 0;
start = PED_MAX (last_part->geom.end + 1,
ped_round_down_to (dev->length, cyl_size));
end = dev->length - 1;
if (start < end) {
if (!add_metadata_part (disk, PED_PARTITION_NORMAL, start, end))
return 0;
}
return 1;
}
static int
msdos_alloc_metadata (PedDisk* disk)
{
PedPartition* ext_part;
PED_ASSERT (disk != NULL, return 0);
PED_ASSERT (disk->dev != NULL, return 0);
if (!add_startend_metadata (disk))
return 0;
ext_part = ped_disk_extended_partition (disk);
if (ext_part) {
int i;
PedSector start, end;
PedCHSGeometry bios_geom;
for (i=5; 1; i++) {
PedPartition* log_part;
log_part = ped_disk_get_partition (disk, i);
if (!log_part)
break;
if (!add_logical_part_metadata (disk, log_part))
return 0;
}
partition_probe_bios_geometry (ext_part, &bios_geom);
start = ext_part->geom.start;
end = start + bios_geom.sectors - 1;
if (ext_part->part_list)
end = PED_MIN (end,
ext_part->part_list->geom.start - 1);
if (!add_metadata_part (disk, PED_PARTITION_LOGICAL,
start, end))
return 0;
}
return 1;
}
static int
next_primary (const PedDisk* disk)
{
int i;
for (i=1; i<=4; i++) {
if (!ped_disk_get_partition (disk, i))
return i;
}
return 0;
}
static int
next_logical (const PedDisk* disk)
{
int i;
for (i=5; 1; i++) {
if (!ped_disk_get_partition (disk, i))
return i;
}
}
static int
msdos_partition_enumerate (PedPartition* part)
{
PED_ASSERT (part != NULL, return 0);
PED_ASSERT (part->disk != NULL, return 0);
/* don't re-number a primary partition */
if (part->num != -1 && part->num <= 4)
return 1;
part->num = -1;
if (part->type & PED_PARTITION_LOGICAL)
part->num = next_logical (part->disk);
else
part->num = next_primary (part->disk);
return 1;
}
static int
msdos_get_max_primary_partition_count (const PedDisk* disk)
{
return 4;
}
static PedDiskOps msdos_disk_ops = {
probe: msdos_probe,
#ifndef DISCOVER_ONLY
clobber: msdos_clobber,
#else
clobber: NULL,
#endif
alloc: msdos_alloc,
duplicate: msdos_duplicate,
free: msdos_free,
read: msdos_read,
#ifndef DISCOVER_ONLY
write: msdos_write,
#else
write: NULL,
#endif
partition_new: msdos_partition_new,
partition_duplicate: msdos_partition_duplicate,
partition_destroy: msdos_partition_destroy,
partition_set_system: msdos_partition_set_system,
partition_set_flag: msdos_partition_set_flag,
partition_get_flag: msdos_partition_get_flag,
partition_is_flag_available: msdos_partition_is_flag_available,
partition_set_name: NULL,
partition_get_name: NULL,
partition_align: msdos_partition_align,
partition_enumerate: msdos_partition_enumerate,
alloc_metadata: msdos_alloc_metadata,
get_max_primary_partition_count:
msdos_get_max_primary_partition_count
};
static PedDiskType msdos_disk_type = {
next: NULL,
name: "msdos",
ops: &msdos_disk_ops,
features: PED_DISK_TYPE_EXTENDED
};
void
ped_disk_msdos_init ()
{
PED_ASSERT (sizeof (DosRawPartition) == 16, return);
PED_ASSERT (sizeof (DosRawTable) == 512, return);
ped_disk_type_register (&msdos_disk_type);
}
void
ped_disk_msdos_done ()
{
ped_disk_type_unregister (&msdos_disk_type);
}