 /*
    libparted - a library for manipulating disk partitions
    Copyright (C) 1999-2003, 2005, 2007-2014, 2019-2023 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 3 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, see <http://www.gnu.org/licenses/>.
*/

/** \file disk.c */

/**
 * \addtogroup PedDisk
 *
 * \brief Disk label access.
 *
 * Most programs will need to use ped_disk_new() or ped_disk_new_fresh() to get
 * anything done.  A PedDisk is always associated with a device and has a
 * partition table.  There are different types of partition tables (or disk
 * labels).  These are represented by the PedDiskType enumeration.
 *
 * @{
 */

#include <config.h>

#include <parted/parted.h>
#include <parted/debug.h>
#include <stdbool.h>
#include <limits.h>

#include "architecture.h"
#include "labels/pt-tools.h"

#if ENABLE_NLS
#  include <libintl.h>
#  define _(String) dgettext (PACKAGE, String)
#  define N_(String) (String)
#else
#  define _(String) (String)
#  define N_(String) (String)
#endif /* ENABLE_NLS */

/* UPDATE MODE functions */
#ifdef DEBUG
static int _disk_check_sanity (PedDisk* disk);
#endif
static int _disk_push_update_mode (PedDisk* disk);
static int _disk_pop_update_mode (PedDisk* disk);
static int _disk_raw_insert_before (PedDisk* disk, PedPartition* loc,
				    PedPartition* part);
static int _disk_raw_insert_after (PedDisk* disk, PedPartition* loc,
				   PedPartition* part);
static int _disk_raw_remove (PedDisk* disk, PedPartition* part);
static int _disk_raw_add (PedDisk* disk, PedPartition* part);

static PedDiskType*	disk_types = NULL;

void
ped_disk_type_register (PedDiskType* disk_type)
{
	PED_ASSERT (disk_type != NULL);
	PED_ASSERT (disk_type->ops != NULL);
	PED_ASSERT (disk_type->name != NULL);

        disk_type->next = disk_types;
        disk_types =  disk_type;
}

void
ped_disk_type_unregister (PedDiskType* disk_type)
{
	PedDiskType*	walk;
	PedDiskType*	last = NULL;

	PED_ASSERT (disk_types != NULL);
	PED_ASSERT (disk_type != NULL);

	for (walk = disk_types; walk && walk != disk_type;
                last = walk, walk = walk->next);

	PED_ASSERT (walk != NULL);
	if (last)
		((struct _PedDiskType*) last)->next = disk_type->next;
	else
		disk_types = disk_type->next;
}

/**
 * Return the next disk type registers, after "type".  If "type" is
 * NULL, returns the first disk type.
 *
 * \return Next disk; NULL if "type" is the last registered disk type.
 */
PedDiskType*
ped_disk_type_get_next (PedDiskType const *type)
{
	if (type)
		return type->next;
	else
		return disk_types;
}

/**
 * Return the disk type with a name of "name".
 *
 * \return Disk type; NULL if no match.
 */
PedDiskType*
ped_disk_type_get (const char* name)
{
	PedDiskType*	walk = NULL;

	PED_ASSERT (name != NULL);

	for (walk = ped_disk_type_get_next (NULL); walk;
	     walk = ped_disk_type_get_next (walk))
			if (strcasecmp (walk->name, name) == 0)
					break;

	return walk;
}

/**
 * Return the type of partition table detected on "dev".
 *
 * \return Type; NULL if none was detected.
 */
PedDiskType*
ped_disk_probe (PedDevice* dev)
{
        PedDiskType* walk = NULL;

        PED_ASSERT (dev != NULL);

        if (!ped_device_open (dev))
                return NULL;

        ped_exception_fetch_all ();
        for (walk = ped_disk_type_get_next (NULL); walk;
             walk = ped_disk_type_get_next (walk))
          {
                if (getenv ("PARTED_DEBUG")) {
                        fprintf (stderr, "probe label: %s\n",
                                 walk->name);
                        fflush (stderr);
                }
                if (walk->ops->probe (dev))
                        break;
          }

        if (ped_exception)
                ped_exception_catch ();
        ped_exception_leave_all ();

        ped_device_close (dev);
        return walk;
}

/**
 * Read the partition table off a device (if one is found).
 *
 * \warning May modify \p dev->cylinders, \p dev->heads and \p dev->sectors
 *      if the partition table indicates that the existing values
 *      are incorrect.
 *
 * \return A new \link _PedDisk PedDisk \endlink object;
 *         NULL on failure (e.g. partition table not detected).
 */
PedDisk*
ped_disk_new (PedDevice* dev)
{
	PedDiskType*	type;
	PedDisk*	disk;

	PED_ASSERT (dev != NULL);

	if (!ped_device_open (dev))
		goto error;

	type = ped_disk_probe (dev);
	if (!type) {
		ped_exception_throw (PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
			_("%s: unrecognised disk label"),
			dev->path);
		goto error_close_dev;
	}
	disk = ped_disk_new_fresh (dev, type);
	if (!disk)
		goto error_close_dev;
	if (!type->ops->read (disk))
		goto error_destroy_disk;
	disk->needs_clobber = 0;
	ped_device_close (dev);
	return disk;

error_destroy_disk:
	ped_disk_destroy (disk);
error_close_dev:
	ped_device_close (dev);
error:
	return NULL;
}

static int
_add_duplicate_part (PedDisk* disk, PedPartition* old_part)
{
	PedPartition*	new_part;
	int ret;

	new_part = disk->type->ops->partition_duplicate (old_part);
	if (!new_part)
		goto error;
	new_part->disk = disk;

	if (!_disk_push_update_mode (disk))
		goto error_destroy_new_part;
	ret = _disk_raw_add (disk, new_part);
	if (!_disk_pop_update_mode (disk))
		goto error_destroy_new_part;
	if (!ret)
		goto error_destroy_new_part;
#ifdef DEBUG
	if (!_disk_check_sanity (disk))
		goto error_destroy_new_part;
#endif
	return 1;

error_destroy_new_part:
	ped_partition_destroy (new_part);
error:
	return 0;
}

/**
 * Clone a \link _PedDisk PedDisk \endlink object.
 *
 * \return Deep copy of \p old_disk, NULL on failure.
 */
PedDisk*
ped_disk_duplicate (const PedDisk* old_disk)
{
	PedDisk*	new_disk;
	PedPartition*	old_part;

	PED_ASSERT (old_disk != NULL);
	PED_ASSERT (!old_disk->update_mode);
	PED_ASSERT (old_disk->type->ops->duplicate != NULL);
	PED_ASSERT (old_disk->type->ops->partition_duplicate != NULL);

	new_disk = old_disk->type->ops->duplicate (old_disk);
	if (!new_disk)
		goto error;

	if (!_disk_push_update_mode (new_disk))
		goto error_destroy_new_disk;
	for (old_part = ped_disk_next_partition (old_disk, NULL); old_part;
	     old_part = ped_disk_next_partition (old_disk, old_part)) {
		if (ped_partition_is_active (old_part)) {
			if (!_add_duplicate_part (new_disk, old_part)){
				_disk_pop_update_mode (new_disk);
				goto error_destroy_new_disk;
			}
		}
	}
	if (!_disk_pop_update_mode (new_disk))
		goto error_destroy_new_disk;

        new_disk->needs_clobber = old_disk->needs_clobber;

	return new_disk;

error_destroy_new_disk:
	ped_disk_destroy (new_disk);
error:
	return NULL;
}

/* Given a partition table type NAME, e.g., "gpt", return its PedDiskType
   handle.  If no known type has a name matching NAME, return NULL.  */
static PedDiskType const * _GL_ATTRIBUTE_PURE
find_disk_type (char const *name)
{
  PedDiskType const *t;
  for (t = ped_disk_type_get_next (NULL); t; t = ped_disk_type_get_next (t))
    {
      if (strcmp (t->name, name) == 0)
        return t;
    }
  return NULL;
}

/**
 * Remove all identifying signatures of a partition table,
 *
 * \return 0 on error, 1 otherwise.
 *
 * \sa ped_disk_clobber()
 */
int
ped_disk_clobber (PedDevice* dev)
{
	PED_ASSERT (dev != NULL);

	if (!ped_device_open (dev))
		goto error;

        PedDiskType const *gpt = find_disk_type ("gpt");
	PED_ASSERT (gpt != NULL);

        /* If there is a GPT table, don't clobber the protective MBR.  */
        bool is_gpt = gpt->ops->probe (dev);
        PedSector first_sector = (is_gpt ? 1 : 0);

	/* How many sectors to zero out at each end.
	   This must be large enough to zero out the magic bytes
	   starting at offset 8KiB on a DASD partition table.
	   Doing the same from the end of the disk is probably
	   overkill, but at least on GPT, we do need to zero out
	   the final sector.  */
	const PedSector n_sectors = 9 * 1024 / dev->sector_size + 1;

	/* Clear the first few.  */
	PedSector n = n_sectors;
	if (dev->length < first_sector + n_sectors)
	  n = dev->length - first_sector;
        if (!ptt_clear_sectors (dev, first_sector, n))
          goto error_close_dev;

	/* Clear the last few.  */
	PedSector t = (dev->length -
		       (n_sectors < dev->length ? n_sectors : 1));

        /* Don't clobber the pMBR if we have a pathologically small disk.  */
        if (t < first_sector)
          t = first_sector;
        if (!ptt_clear_sectors (dev, t, dev->length - t))
          goto error_close_dev;

	ped_device_close (dev);
	return 1;

error_close_dev:
	ped_device_close (dev);
error:
	return 0;
}

/**
 * Create a new partition table on \p dev.
 *
 * This new partition table is only created in-memory, and nothing is written
 * to disk until ped_disk_commit_to_dev() is called.
 *
 * \return The newly constructed \link _PedDisk PedDisk \endlink,
 *      NULL on failure.
 */
PedDisk*
ped_disk_new_fresh (PedDevice* dev, const PedDiskType* type)
{
	PedDisk*	disk;

	PED_ASSERT (dev != NULL);
	PED_ASSERT (type != NULL);
	PED_ASSERT (type->ops->alloc != NULL);
	PedCHSGeometry*	bios_geom = &dev->bios_geom;
	PED_ASSERT (bios_geom->sectors != 0);
	PED_ASSERT (bios_geom->heads != 0);

	disk = type->ops->alloc (dev);
	if (!disk)
       		goto error;
        if (!_disk_pop_update_mode (disk))
                goto error_destroy_disk;
	PED_ASSERT (disk->update_mode == 0);

	disk->needs_clobber = 1;
	return disk;

error_destroy_disk:
        ped_disk_destroy (disk);
error:
	return NULL;
}

PedDisk*
_ped_disk_alloc (const PedDevice* dev, const PedDiskType* disk_type)
{
	PedDisk*	disk;

	disk = (PedDisk*) ped_malloc (sizeof (PedDisk));
	if (!disk)
		goto error;

	disk->dev = (PedDevice*)dev;
	disk->type = disk_type;
	disk->update_mode = 1;
	disk->part_list = NULL;
	disk->needs_clobber = 0;
	return disk;

error:
	return NULL;
}

void
_ped_disk_free (PedDisk* disk)
{
	_disk_push_update_mode (disk);
	ped_disk_delete_all (disk);
	free (disk);
}

/**
 * Close \p disk.
 *
 * What this function does depends on the PedDiskType of \p disk,
 * but you can generally assume that outstanding writes are flushed
 * (this mainly means that _ped_disk_free is called).
 */
void
ped_disk_destroy (PedDisk* disk)
{
	PED_ASSERT (disk != NULL);
	PED_ASSERT (!disk->update_mode);

	disk->type->ops->free (disk);
}

/**
 * Tell the operating system kernel about the partition table layout
 * of \p disk.
 *
 * This is rather loosely defined: for example, on old versions of Linux,
 * it simply calls the BLKRRPART ioctl, which tells the kernel to
 * reread the partition table. On newer versions (2.4.x), it will
 * use the new blkpg interface to tell Linux where each partition
 * starts/ends, etc. In this case, Linux does not need to have support for
 * a specific type of partition table.
 *
 * \return 0 on failure, 1 otherwise.
 */
int
ped_disk_commit_to_os (PedDisk* disk)
{
	PED_ASSERT (disk != NULL);

	if (!ped_device_open (disk->dev))
		goto error;
	if (!ped_architecture->disk_ops->disk_commit (disk))
		goto error_close_dev;
	ped_device_close (disk->dev);
	return 1;

error_close_dev:
	ped_device_close (disk->dev);
error:
	return 0;
}

/**
 * Write the changes made to the in-memory description
 * of a partition table to the device.
 *
 * \return 0 on failure, 1 otherwise.
 */
int
ped_disk_commit_to_dev (PedDisk* disk)
{
	PED_ASSERT (disk != NULL);
	PED_ASSERT (!disk->update_mode);

	if (!disk->type->ops->write) {
		ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_CANCEL,
			_("This libparted doesn't have write support for "
			  "%s.  Perhaps it was compiled read-only."),
			disk->type->name);
		goto error;
	}

	if (!ped_device_open (disk->dev))
		goto error;

	if (disk->needs_clobber) {
		if (!ped_disk_clobber (disk->dev))
			goto error_close_dev;
		disk->needs_clobber = 0;
	}
	if (!disk->type->ops->write (disk))
		goto error_close_dev;
	ped_device_close (disk->dev);
	return 1;

error_close_dev:
	ped_device_close (disk->dev);
error:
	return 0;
}

/*
 * This function writes the in-memory changes to a partition table to
 * disk and informs the operating system of the changes.
 *
 * \note Equivalent to calling first ped_disk_commit_to_dev(), then
 *      ped_disk_commit_to_os().
 *
 * \return 0 on failure, 1 otherwise.
 */
int
ped_disk_commit (PedDisk* disk)
{
        /* Open the device here, so that the underlying fd is not closed
           between commit_to_dev and commit_to_os (closing causes unwanted
           udev events to be sent under Linux). */
	if (!ped_device_open (disk->dev))
		goto error;

	if (!ped_disk_commit_to_dev (disk))
		goto error_close_dev;

	if (!ped_disk_commit_to_os (disk))
		goto error_close_dev;

	ped_device_close (disk->dev);
	return 1;

error_close_dev:
	ped_device_close (disk->dev);
error:
	return 0;
}

/**
 * \addtogroup PedPartition
 *
 * @{
 */

/**
 * Check whether a partition is mounted or busy in some
 * other way.
 *
 * \note An extended partition is busy if any logical partitions are mounted.
 *
 * \return \c 1 if busy.
 */
int
ped_partition_is_busy (const PedPartition* part)
{
	PED_ASSERT (part != NULL);

	return ped_architecture->disk_ops->partition_is_busy (part);
}

/**
 * Return a path that can be used to address the partition in the
 * operating system.
 */
char*
ped_partition_get_path (const PedPartition* part)
{
	PED_ASSERT (part != NULL);

	return ped_architecture->disk_ops->partition_get_path (part);
}

/** @} */

/**
 * \addtogroup PedDisk
 *
 * @{
 */

/**
 * Perform a sanity check on a partition table.
 *
 * \note The check performed is generic (i.e. it does not depends on the label
 *      type of the disk.
 *
 * \throws PED_EXCEPTION_WARNING if a partition type ID does not match the file
 *      system on it.
 *
 * \return 0 if the check fails, 1 otherwise.
 */
int
ped_disk_check (const PedDisk* disk)
{
	PedPartition*	walk;

	PED_ASSERT (disk != NULL);

	for (walk = disk->part_list; walk;
	     walk = ped_disk_next_partition (disk, walk)) {
		const PedFileSystemType*	fs_type = walk->fs_type;
		PedGeometry*			geom;
		PedSector			length_error;
		PedSector			max_length_error;

		if (!ped_partition_is_active (walk) || !fs_type)
			continue;

		geom = ped_file_system_probe_specific (fs_type, &walk->geom);
		if (!geom)
			continue;

		length_error = llabs (walk->geom.length - geom->length);
		max_length_error = PED_MAX (4096, walk->geom.length / 100);
                bool ok = (ped_geometry_test_inside (&walk->geom, geom)
                           && length_error <= max_length_error);
                char *fs_size = ped_unit_format (disk->dev, geom->length);
                ped_geometry_destroy (geom);
                if (!ok) {
			char* part_size = ped_unit_format (disk->dev,
                                                           walk->geom.length);
			PedExceptionOption choice;
			choice = ped_exception_throw (
				PED_EXCEPTION_WARNING,
				PED_EXCEPTION_IGNORE_CANCEL,
				_("Partition %d is %s, but the file system is "
				  "%s."),
				walk->num, part_size, fs_size);

			free (part_size);

			free (fs_size);
			fs_size = NULL;

			if (choice != PED_EXCEPTION_IGNORE)
				return 0;
		}
		free (fs_size);
	}

	return 1;
}

/**
 * This function checks if a particular type of partition table supports
 * a feature.
 *
 * \return 1 if \p disk_type supports \p feature, 0 otherwise.
 */
int
ped_disk_type_check_feature (const PedDiskType* disk_type,
			     PedDiskTypeFeature feature)
{
	return (disk_type->features & feature) != 0;
}

/**
 * Get the number of primary partitions.
 */
int
ped_disk_get_primary_partition_count (const PedDisk* disk)
{
	PedPartition*	walk;
	int		count = 0;

	PED_ASSERT (disk != NULL);

	for (walk = disk->part_list; walk;
	     walk = ped_disk_next_partition (disk, walk)) {
		if (ped_partition_is_active (walk)
				&& ! (walk->type & PED_PARTITION_LOGICAL))
			count++;
	}

	return count;
}

/**
 * Get the highest available partition number on \p disk.
 */
int
ped_disk_get_last_partition_num (const PedDisk* disk)
{
	PedPartition*	walk;
	int		highest = -1;

	PED_ASSERT (disk != NULL);

	for (walk = disk->part_list; walk;
	     walk = ped_disk_next_partition (disk, walk)) {
		if (walk->num > highest)
			highest = walk->num;
	}

	return highest;
}

/**
 * Get the highest supported partition number on \p disk.
 *
 * \return 0 if call fails. 1 otherwise.
 */
bool
ped_disk_get_max_supported_partition_count(const PedDisk* disk, int* supported)
{
	PED_ASSERT(disk != NULL);
	PED_ASSERT(disk->type->ops->get_max_supported_partition_count != NULL);

	return disk->type->ops->get_max_supported_partition_count(disk, supported);
}

/**
 * Get the alignment needed for partition boundaries on this disk.
 * The returned alignment describes the alignment for the start sector of the
 * partition, for all disklabel types which require alignment, except Sun
 * disklabels, the end sector must be aligned too. To get the end sector
 * alignment decrease the PedAlignment offset by 1.
 *
 * \return NULL on error, otherwise a pointer to a dynamically allocated
 *         alignment.
 */
PedAlignment*
ped_disk_get_partition_alignment(const PedDisk *disk)
{
        /* disklabel handlers which don't need alignment don't define this */
        if (!disk->type->ops->get_partition_alignment)
                return ped_alignment_duplicate(ped_alignment_any);

        return disk->type->ops->get_partition_alignment(disk);
}

/**
 * Get the maximum number of (primary) partitions the disk label supports.
 *
 * For example, MacIntosh partition maps can have different sizes,
 * and accordingly support a different number of partitions.
 */
int
ped_disk_get_max_primary_partition_count (const PedDisk* disk)
{
	PED_ASSERT (disk->type != NULL);
	PED_ASSERT (disk->type->ops->get_max_primary_partition_count != NULL);

	return disk->type->ops->get_max_primary_partition_count (disk);
}

/**
 * Set the state (\c 1 or \c 0) of a flag on a disk.
 *
 * \note It is an error to call this on an unavailable flag -- use
 * ped_disk_is_flag_available() to determine which flags are available
 * for a given disk label.
 *
 * \throws PED_EXCEPTION_ERROR if the requested flag is not available for this
 *      label.
 */
int
ped_disk_set_flag(PedDisk *disk, PedDiskFlag flag, int state)
{
        int ret;

        PED_ASSERT (disk != NULL);

        PedDiskOps *ops = disk->type->ops;

        if (!_disk_push_update_mode(disk))
                return 0;

        if (!ped_disk_is_flag_available(disk, flag)) {
                ped_exception_throw (
                        PED_EXCEPTION_ERROR,
                        PED_EXCEPTION_CANCEL,
                        "The flag '%s' is not available for %s disk labels.",
                        ped_disk_flag_get_name(flag),
                        disk->type->name);
                _disk_pop_update_mode(disk);
                return 0;
        }

        ret = ops->disk_set_flag(disk, flag, state);

        if (!_disk_pop_update_mode (disk))
                return 0;

        return ret;
}

/**
 * Get the state (\c 1 or \c 0) of a flag on a disk.
 */
int
ped_disk_get_flag(const PedDisk *disk, PedDiskFlag flag)
{
        PED_ASSERT (disk != NULL);

        PedDiskOps *ops = disk->type->ops;

        if (!ped_disk_is_flag_available(disk, flag))
                return 0;

        return ops->disk_get_flag(disk, flag);
}

/**
 * Check whether a given flag is available on a disk.
 *
 * \return \c 1 if the flag is available.
 */
int
ped_disk_is_flag_available(const PedDisk *disk, PedDiskFlag flag)
{
        PED_ASSERT (disk != NULL);

        PedDiskOps *ops = disk->type->ops;

        if (!ops->disk_is_flag_available)
                return 0;

        return ops->disk_is_flag_available(disk, flag);
}

/**
 * Returns a name for a \p flag, e.g. PED_DISK_CYLINDER_ALIGNMENT will return
 * "cylinder_alignment".
 *
 * \note The returned string will be in English.  However,
 * translations are provided, so the caller can call
 * dgettext("parted", RESULT) on the result.
 */
const char *
ped_disk_flag_get_name(PedDiskFlag flag)
{
        switch (flag) {
        case PED_DISK_CYLINDER_ALIGNMENT:
                return N_("cylinder_alignment");
        case PED_DISK_GPT_PMBR_BOOT:
                return N_("pmbr_boot");
        default:
                ped_exception_throw (
                        PED_EXCEPTION_BUG,
                        PED_EXCEPTION_CANCEL,
                        _("Unknown disk flag, %d."),
                        flag);
                return NULL;
        }
}

/**
 * Returns the flag associated with \p name.
 *
 * \p name can be the English
 * string, or the translation for the native language.
 */
PedDiskFlag
ped_disk_flag_get_by_name(const char *name)
{
        PedDiskFlag flag;

        for (flag = ped_disk_flag_next(0); flag;
             flag = ped_disk_flag_next(flag)) {
                const char *flag_name = ped_disk_flag_get_name(flag);
                if (strcasecmp(name, flag_name) == 0
                    || strcasecmp(name, _(flag_name)) == 0)
                        return flag;
        }

        return 0;
}

/**
 * Iterates through all disk flags.
 *
 * ped_disk_flag_next(0) returns the first flag
 *
 * \return the next flag, or 0 if there are no more flags
 */
PedDiskFlag
ped_disk_flag_next(PedDiskFlag flag)
{
        return (flag + 1) % (PED_DISK_LAST_FLAG + 1);
}

static int
_assert_disk_uuid_feature (const PedDiskType* disk_type)
{
        if (!ped_disk_type_check_feature (
                        disk_type, PED_DISK_TYPE_DISK_UUID)) {
                ped_exception_throw (
                        PED_EXCEPTION_ERROR,
                        PED_EXCEPTION_CANCEL,
                        "%s disk labels do not support disk uuids.",
                        disk_type->name);
                return 0;
        }
        return 1;
}

/**
 * Get the uuid of the disk \p disk. This will only work if the disk label
 * supports it.
 */
uint8_t*
ped_disk_get_uuid (const PedDisk *disk)
{
        PED_ASSERT (disk != NULL);

        if (!_assert_disk_uuid_feature (disk->type))
                return NULL;

        PED_ASSERT (disk->type->ops->disk_get_uuid != NULL);
        return disk->type->ops->disk_get_uuid (disk);
}

/**
 * \internal We turned a really nasty bureaucracy problem into an elegant maths
 * problem :-)  Basically, there are some constraints to a partition's
 * geometry:
 *
 * (1) it must start and end on a "disk" block, determined by the disk label
 * (not the hardware).  (constraint represented by a PedAlignment)
 *
 * (2) if we're resizing a partition, we MIGHT need to keep each block aligned.
 * Eg: if an ext2 file system has 4k blocks, then we can only move the start
 * by a multiple of 4k.  (constraint represented by a PedAlignment)
 *
 * (3) we need to keep the start and end within the device's physical
 * boundaries.  (constraint represented by a PedGeometry)
 *
 * Satisfying (1) and (2) simultaneously required a bit of fancy maths ;-)  See
 * ped_alignment_intersect()
 *
 * The application of these constraints is in disk_*.c's *_partition_align()
 * function.
 */
static int
_partition_align (PedPartition* part, const PedConstraint* constraint)
{
	const PedDiskType*	disk_type;

	PED_ASSERT (part != NULL);
	PED_ASSERT (part->num != -1);
	PED_ASSERT (part->disk != NULL);
	disk_type = part->disk->type;
	PED_ASSERT (disk_type != NULL);
	PED_ASSERT (disk_type->ops->partition_align != NULL);
	PED_ASSERT (part->disk->update_mode);

	if (part->disk->needs_clobber)
		return 1; /* do not attempt to align partitions while reading them */
	return disk_type->ops->partition_align (part, constraint);
}

static int
_partition_enumerate (PedPartition* part)
{
	const PedDiskType*	disk_type;

	PED_ASSERT (part != NULL);
	PED_ASSERT (part->disk != NULL);
	disk_type = part->disk->type;
	PED_ASSERT (disk_type != NULL);
	PED_ASSERT (disk_type->ops->partition_enumerate != NULL);

	return disk_type->ops->partition_enumerate (part);
}

/**
 * Gives all the (active) partitions a number.  It should preserve the numbers
 * and orders as much as possible.
 */
static int
ped_disk_enumerate_partitions (PedDisk* disk)
{
	PedPartition*	walk;
	int		i;
	int		end;

	PED_ASSERT (disk != NULL);

/* first "sort" already-numbered partitions.  (e.g. if a logical partition
 * is removed, then all logical partitions that were number higher MUST be
 * renumbered)
 */
	end = ped_disk_get_last_partition_num (disk);
	for (i=1; i<=end; i++) {
		walk = ped_disk_get_partition (disk, i);
		if (walk) {
			if (!_partition_enumerate (walk))
				return 0;
		}
	}

/* now, number un-numbered partitions */
	for (walk = disk->part_list; walk;
	     walk = ped_disk_next_partition (disk, walk)) {
		if (ped_partition_is_active (walk) && walk->num == -1) {
			if (!_partition_enumerate (walk))
				return 0;
		}
	}

	return 1;
}

static int
_disk_remove_metadata (PedDisk* disk)
{
	PedPartition*	walk = NULL;
	PedPartition*	next;

	PED_ASSERT (disk != NULL);

	next = ped_disk_next_partition (disk, walk);

	while (next) {
		walk = next;
		while (1) {
			next = ped_disk_next_partition (disk, next);
			if (!next || next->type & PED_PARTITION_METADATA)
				break;
		}
		if (walk->type & PED_PARTITION_METADATA)
			ped_disk_delete_partition (disk, walk);
	}
	return 1;
}

static int
_disk_alloc_metadata (PedDisk* disk)
{
	PED_ASSERT (disk != NULL);

	if (!disk->update_mode)
		_disk_remove_metadata (disk);

	return disk->type->ops->alloc_metadata (disk);
}

static int
_disk_remove_freespace (PedDisk* disk)
{
	PedPartition*	walk;
	PedPartition*	next;

	walk = ped_disk_next_partition (disk, NULL);
	for (; walk; walk = next) {
		next = ped_disk_next_partition (disk, walk);

		if (walk->type & PED_PARTITION_FREESPACE) {
			_disk_raw_remove (disk, walk);
			ped_partition_destroy (walk);
		}
	}

	return 1;
}

static int
_alloc_extended_freespace (PedDisk* disk)
{
	PedSector	last_end;
	PedPartition*	walk;
	PedPartition*	last;
	PedPartition*	free_space;
	PedPartition*	extended_part;

	extended_part = ped_disk_extended_partition (disk);
	if (!extended_part)
		return 1;

	last_end = extended_part->geom.start;
	last = NULL;

	for (walk = extended_part->part_list; walk; walk = walk->next) {
		if (walk->geom.start > last_end + 1) {
			free_space = ped_partition_new (
					disk,
					PED_PARTITION_FREESPACE
						| PED_PARTITION_LOGICAL,
					NULL,
					last_end + 1, walk->geom.start - 1);
			_disk_raw_insert_before (disk, walk, free_space);
		}

		last = walk;
		last_end = last->geom.end;
	}

	if (last_end < extended_part->geom.end) {
		free_space = ped_partition_new (
				disk,
				PED_PARTITION_FREESPACE | PED_PARTITION_LOGICAL,
				NULL,
				last_end + 1, extended_part->geom.end);

		if (last)
			return _disk_raw_insert_after (disk, last, free_space);
		else
			extended_part->part_list = free_space;
	}

	return 1;
}

static int
_disk_alloc_freespace (PedDisk* disk)
{
	PedSector	last_end;
	PedPartition*	walk;
	PedPartition*	last;
	PedPartition*	free_space;

	if (!_disk_remove_freespace (disk))
		return 0;
	if (!_alloc_extended_freespace (disk))
		return 0;

	last = NULL;
	last_end = -1;

	for (walk = disk->part_list; walk; walk = walk->next) {
		if (walk->geom.start > last_end + 1) {
			free_space = ped_partition_new (disk,
					PED_PARTITION_FREESPACE, NULL,
					last_end + 1, walk->geom.start - 1);
			_disk_raw_insert_before (disk, walk, free_space);
		}

		last = walk;
		last_end = last->geom.end;
	}

	if (last_end < disk->dev->length - 1) {
		free_space = ped_partition_new (disk,
					PED_PARTITION_FREESPACE, NULL,
					last_end + 1, disk->dev->length - 1);
		if (last)
			return _disk_raw_insert_after (disk, last, free_space);
		else
			disk->part_list = free_space;
	}

	return 1;
}

/**
 * Update mode: used when updating the internal representation of the partition
 * table.  In update mode, the metadata and freespace placeholder/virtual
 * partitions are removed, making it much easier for various manipulation
 * routines...
 */
static int
_disk_push_update_mode (PedDisk* disk)
{
	if (!disk->update_mode) {
#ifdef DEBUG
		if (!_disk_check_sanity (disk))
			return 0;
#endif

		_disk_remove_freespace (disk);
		disk->update_mode++;
		_disk_remove_metadata (disk);

#ifdef DEBUG
		if (!_disk_check_sanity (disk))
			return 0;
#endif
	} else {
		disk->update_mode++;
	}
	return 1;
}

static int
_disk_pop_update_mode (PedDisk* disk)
{
	PED_ASSERT (disk->update_mode);

	if (disk->update_mode == 1) {
	/* re-allocate metadata BEFORE leaving update mode, to prevent infinite
	 * recursion (metadata allocation requires update mode)
	 */
#ifdef DEBUG
		if (!_disk_check_sanity (disk))
			return 0;
#endif

		_disk_alloc_metadata (disk);
		disk->update_mode--;
		_disk_alloc_freespace (disk);

#ifdef DEBUG
		if (!_disk_check_sanity (disk))
			return 0;
#endif
	} else {
		disk->update_mode--;
	}
	return 1;
}

/** @} */

/**
 * \addtogroup PedPartition
 *
 * \brief Partition access.
 *
 * @{
 */

PedPartition*
_ped_partition_alloc (const PedDisk* disk, PedPartitionType type,
		      const PedFileSystemType* fs_type,
		      PedSector start, PedSector end)
{
	PedPartition*	part;

	PED_ASSERT (disk != NULL);

	part = (PedPartition*) ped_malloc (sizeof (PedPartition));
	if (!part)
		goto error;

	part->prev = NULL;
	part->next = NULL;

	part->disk = (PedDisk*) disk;
	if (!ped_geometry_init (&part->geom, disk->dev, start, end - start + 1))
		goto error_free_part;

	part->num = -1;
	part->type = type;
	part->part_list = NULL;
	part->fs_type = fs_type;

	return part;

error_free_part:
	free (part);
error:
	return NULL;
}

void
_ped_partition_free (PedPartition* part)
{
	free (part);
}

int
_ped_partition_attempt_align (PedPartition* part,
			      const PedConstraint* external,
			      PedConstraint* internal)
{
	PedConstraint*		intersection;
	PedGeometry*		solution;

	intersection = ped_constraint_intersect (external, internal);
	ped_constraint_destroy (internal);
	if (!intersection)
		goto fail;

	solution = ped_constraint_solve_nearest (intersection, &part->geom);
	if (!solution)
		goto fail_free_intersection;
	ped_geometry_set (&part->geom, solution->start, solution->length);
	ped_geometry_destroy (solution);
	ped_constraint_destroy (intersection);
	return 1;

fail_free_intersection:
	ped_constraint_destroy (intersection);
fail:
	return 0;
}

/**
 * Create a new \link _PedPartition PedPartition \endlink on \p disk.
 *
 * \param type One of \p PED_PARTITION_NORMAL, \p PED_PARTITION_EXTENDED,
 *      \p PED_PARTITION_LOGICAL.
 *
 * \note The constructed partition is not added to <tt>disk</tt>'s
 *      partition table. Use ped_disk_add_partition() to do this.
 *
 * \return A new \link _PedPartition PedPartition \endlink object,
 *      NULL on failure.
 *
 * \throws PED_EXCEPTION_ERROR if \p type is \p EXTENDED or \p LOGICAL but the
 *      label does not support this concept.
 */
PedPartition*
ped_partition_new (const PedDisk* disk, PedPartitionType type,
		   const PedFileSystemType* fs_type, PedSector start,
		   PedSector end)
{
	int		supports_extended;
	PedPartition*	part;

	PED_ASSERT (disk != NULL);
	PED_ASSERT (disk->type->ops->partition_new != NULL);

	supports_extended = ped_disk_type_check_feature (disk->type,
			    	PED_DISK_TYPE_EXTENDED);

	if (!supports_extended
	    && (type == PED_PARTITION_EXTENDED
			|| type == PED_PARTITION_LOGICAL)) {
		ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_CANCEL,
			_("%s disk labels do not support extended "
			  "partitions."),
			disk->type->name);
		goto error;
	}

	part = disk->type->ops->partition_new (disk, type, fs_type, start, end);
	if (!part)
		goto error;

	if (fs_type || part->type == PED_PARTITION_EXTENDED) {
		if (!ped_partition_set_system (part, fs_type))
			goto error_destroy_part;
	}
	return part;

error_destroy_part:
	ped_partition_destroy (part);
error:
	return NULL;
}

/**
 * Destroy a \link _PedPartition PedPartition \endlink object.
 *
 * \note Should not be called on a partition that is in a partition table.
 *      Use ped_disk_delete_partition() instead.
 */
void
ped_partition_destroy (PedPartition* part)
{
	PED_ASSERT (part != NULL);
	PED_ASSERT (part->disk != NULL);
	PED_ASSERT (part->disk->type->ops->partition_new != NULL);

	part->disk->type->ops->partition_destroy (part);
}


/**
 * Return whether or not the partition is "active".
 *
 * A partition is active if \p part->type is neither \p PED_PARTITION_METADATA
 * nor \p PED_PARTITION_FREE.
 */
int
ped_partition_is_active (const PedPartition* part)
{
	PED_ASSERT (part != NULL);

	return !(part->type & PED_PARTITION_FREESPACE
		 || part->type & PED_PARTITION_METADATA);
}

/**
 * Set the state (\c 1 or \c 0) of a flag on a partition.
 *
 * Flags are disk label specific, although they have a global
 * "namespace": the flag PED_PARTITION_BOOT, for example, roughly means
 * "this" partition is bootable". But this means different things on different
 * disk labels (and may not be defined on some disk labels). For example,
 * on MS-DOS disk labels, there can only be one boot partition, and this
 * refers to the partition that will be booted from on startup. On PC98
 * disk labels, the user can choose from any bootable partition on startup.
 *
 * \note It is an error to call this on an unavailable flag -- use
 * ped_partition_is_flag_available() to determine which flags are available
 * for a given disk label.
 *
 * \throws PED_EXCEPTION_ERROR if the requested flag is not available for this
 *      label.
 */
int
ped_partition_set_flag (PedPartition* part, PedPartitionFlag flag, int state)
{
	PedDiskOps*	ops;

	PED_ASSERT (part != NULL);
	PED_ASSERT (part->disk != NULL);
	PED_ASSERT (ped_partition_is_active (part));

	ops = part->disk->type->ops;
	PED_ASSERT (ops->partition_set_flag != NULL);
	PED_ASSERT (ops->partition_is_flag_available != NULL);

	if (!ops->partition_is_flag_available (part, flag)) {
		ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_CANCEL,
			"The flag '%s' is not available for %s disk labels.",
			ped_partition_flag_get_name (flag),
			part->disk->type->name);
		return 0;
	}

	return ops->partition_set_flag (part, flag, state);
}

/**
 * Get the state (\c 1 or \c 0) of a flag on a partition.
 *
 * See ped_partition_set_flag() for conditions that must hold.
 *
 * \todo Where's the check for flag availability?
 */
int
ped_partition_get_flag (const PedPartition* part, PedPartitionFlag flag)
{
	PED_ASSERT (part != NULL);
	PED_ASSERT (part->disk != NULL);
	PED_ASSERT (part->disk->type->ops->partition_get_flag != NULL);
	PED_ASSERT (ped_partition_is_active (part));

	return part->disk->type->ops->partition_get_flag (part, flag);
}

/**
 * Check whether a given flag is available on a partition.
 *
 * \return \c 1 if the flag is available.
 */
int
ped_partition_is_flag_available (const PedPartition* part,
	       			 PedPartitionFlag flag)
{
	PED_ASSERT (part != NULL);
	PED_ASSERT (part->disk != NULL);
	PED_ASSERT (part->disk->type->ops->partition_is_flag_available != NULL);
	PED_ASSERT (ped_partition_is_active (part));

	return part->disk->type->ops->partition_is_flag_available (part, flag);
}

/**
 * Sets the system type on the partition to \p fs_type.
 *
 * \note The file system may be opened, to get more information about the
 * file system, e.g. to determine if it's FAT16 or FAT32.
 *
 * \return \c 0 on failure.
 */
int
ped_partition_set_system (PedPartition* part, const PedFileSystemType* fs_type)
{
	const PedDiskType*	disk_type;

	PED_ASSERT (part != NULL);
	PED_ASSERT (ped_partition_is_active (part));
	PED_ASSERT (part->disk != NULL);
	disk_type = part->disk->type;
	PED_ASSERT (disk_type != NULL);
	PED_ASSERT (disk_type->ops != NULL);
	PED_ASSERT (disk_type->ops->partition_set_system != NULL);

	return disk_type->ops->partition_set_system (part, fs_type);
}

static int
_assert_partition_name_feature (const PedDiskType* disk_type)
{
	if (!ped_disk_type_check_feature (
			disk_type, PED_DISK_TYPE_PARTITION_NAME)) {
		ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_CANCEL,
			"%s disk labels do not support partition names.",
			disk_type->name);
		return 0;
	}
	return 1;
}

static int
_assert_partition_type_id_feature (const PedDiskType* disk_type)
{
        if (!ped_disk_type_check_feature (
                        disk_type, PED_DISK_TYPE_PARTITION_TYPE_ID)) {
                ped_exception_throw (
                        PED_EXCEPTION_ERROR,
                        PED_EXCEPTION_CANCEL,
                        "%s disk labels do not support partition type-ids.",
                        disk_type->name);
                return 0;
        }
        return 1;
}

static int
_assert_partition_type_uuid_feature (const PedDiskType* disk_type)
{
        if (!ped_disk_type_check_feature (
                        disk_type, PED_DISK_TYPE_PARTITION_TYPE_UUID)) {
                ped_exception_throw (
                        PED_EXCEPTION_ERROR,
                        PED_EXCEPTION_CANCEL,
                        "%s disk labels do not support partition type-uuids.",
                        disk_type->name);
                return 0;
        }
        return 1;
}

static int
_assert_partition_uuid_feature (const PedDiskType* disk_type)
{
        if (!ped_disk_type_check_feature (
                        disk_type, PED_DISK_TYPE_PARTITION_UUID)) {
                ped_exception_throw (
                        PED_EXCEPTION_ERROR,
                        PED_EXCEPTION_CANCEL,
                        "%s disk labels do not support partition uuids.",
                        disk_type->name);
                return 0;
        }
        return 1;
}

/**
 * Sets the name of a partition.
 *
 * \note This will only work if the disk label supports it.
 *      You can use
 *      \code
 * ped_disk_type_check_feature (part->disk->type, PED_DISK_TYPE_PARTITION_NAME);
 *      \endcode
 *      to check whether this feature is enabled for a label.
 *
 * \note \p name will not be modified by libparted. It can be freed
 *      by the caller immediately after ped_partition_set_name() is called.
 *
 * \return \c 1 on success, \c 0 otherwise.
 */
int
ped_partition_set_name (PedPartition* part, const char* name)
{
	PED_ASSERT (part != NULL);
	PED_ASSERT (part->disk != NULL);
	PED_ASSERT (ped_partition_is_active (part));
	PED_ASSERT (name != NULL);

	if (!_assert_partition_name_feature (part->disk->type))
		return 0;

	PED_ASSERT (part->disk->type->ops->partition_set_name != NULL);
	part->disk->type->ops->partition_set_name (part, name);
	return 1;
}

/**
 * Returns the name of a partition \p part.  This will only work if the disk
 * label supports it.
 *
 * \note The returned string should not be modified.  It should
 *	not be referenced after the partition is destroyed.
 */
const char*
ped_partition_get_name (const PedPartition* part)
{
	PED_ASSERT (part != NULL);
	PED_ASSERT (part->disk != NULL);
	PED_ASSERT (ped_partition_is_active (part));

	if (!_assert_partition_name_feature (part->disk->type))
		return NULL;

	PED_ASSERT (part->disk->type->ops->partition_get_name != NULL);
	return part->disk->type->ops->partition_get_name (part);
}

/**
 * Set the type-id of the partition \p part. This will only work if the disk label
 * supports it.
 */
int
ped_partition_set_type_id (PedPartition *part, uint8_t id)
{
        PED_ASSERT (part != NULL);
        PED_ASSERT (part->disk != NULL);
        PED_ASSERT (ped_partition_is_active (part));

        if (!_assert_partition_type_id_feature (part->disk->type))
                return 0;

        PED_ASSERT (part->disk->type->ops->partition_set_type_id != NULL);
        return part->disk->type->ops->partition_set_type_id (part, id);
}

/**
 * Get the type-id of the partition \p part. This will only work if the disk label
 * supports it.
 */
uint8_t
ped_partition_get_type_id (const PedPartition *part)
{
        PED_ASSERT (part != NULL);
        PED_ASSERT (part->disk != NULL);
        PED_ASSERT (ped_partition_is_active (part));

        if (!_assert_partition_type_id_feature (part->disk->type))
                return 0;

        PED_ASSERT (part->disk->type->ops->partition_get_type_id != NULL);
        return part->disk->type->ops->partition_get_type_id (part);
}

/**
 * Set the type-uuid of the partition \p part. This will only work if the disk label
 * supports it.
 */
int
ped_partition_set_type_uuid (PedPartition *part, const uint8_t* uuid)
{
        PED_ASSERT (part != NULL);
        PED_ASSERT (part->disk != NULL);
        PED_ASSERT (ped_partition_is_active (part));

        if (!_assert_partition_type_uuid_feature (part->disk->type))
                return 0;

        PED_ASSERT (part->disk->type->ops->partition_set_type_uuid != NULL);
        return part->disk->type->ops->partition_set_type_uuid (part, uuid);
}

/**
 * Get the type-uuid of the partition \p part. This will only work if the disk label
 * supports it.
 */
uint8_t*
ped_partition_get_type_uuid (const PedPartition *part)
{
        PED_ASSERT (part != NULL);
        PED_ASSERT (part->disk != NULL);
        PED_ASSERT (ped_partition_is_active (part));

        if (!_assert_partition_type_uuid_feature (part->disk->type))
                return NULL;

        PED_ASSERT (part->disk->type->ops->partition_get_type_uuid != NULL);
        return part->disk->type->ops->partition_get_type_uuid (part);
}

/**
 * Get the uuid of the partition \p part. This will only work if the disk label
 * supports it.
 */
uint8_t*
ped_partition_get_uuid (const PedPartition *part)
{
        PED_ASSERT (part != NULL);
        PED_ASSERT (part->disk != NULL);
        PED_ASSERT (ped_partition_is_active (part));

        if (!_assert_partition_uuid_feature (part->disk->type))
                return NULL;

        PED_ASSERT (part->disk->type->ops->partition_get_uuid != NULL);
        return part->disk->type->ops->partition_get_uuid (part);
}

/** @} */

/**
 * \addtogroup PedDisk
 *
 * @{
 */

PedPartition*
ped_disk_extended_partition (const PedDisk* disk)
{
	PedPartition*		walk;

	PED_ASSERT (disk != NULL);

	for (walk = disk->part_list; walk; walk = walk->next) {
		if (walk->type == PED_PARTITION_EXTENDED)
			break;
	}
	return walk;
}

/**
 * Return the next partition after \p part on \p disk. If \p part is \c NULL,
 * return the first partition. If \p part is the last partition, returns
 * \c NULL. If \p part is an extended partition, returns the first logical
 * partition. If this is called repeatedly passing the return value as \p part,
 * a depth-first traversal is executed.
 *
 * \return The next partition, \c NULL if no more partitions left.
 */
PedPartition*
ped_disk_next_partition (const PedDisk* disk, const PedPartition* part)
{
	PED_ASSERT (disk != NULL);

	if (!part)
		return disk->part_list;
	if (part->type == PED_PARTITION_EXTENDED)
		return part->part_list ? part->part_list : part->next;
	if (part->next)
		return part->next;
	if (part->type & PED_PARTITION_LOGICAL) {
		if (!ped_disk_extended_partition (disk))
			return NULL;
		return ped_disk_extended_partition (disk)->next;
	}
	return NULL;
}

/** @} */

#ifdef DEBUG
static int _GL_ATTRIBUTE_PURE
_disk_check_sanity (PedDisk* disk)
{
	PedPartition*	walk;

	PED_ASSERT (disk != NULL);

	for (walk = disk->part_list; walk; walk = walk->next) {
		PED_ASSERT (!(walk->type & PED_PARTITION_LOGICAL));
		PED_ASSERT (!walk->prev || walk->prev->next == walk);
	}

	if (!ped_disk_extended_partition (disk))
		return 1;

	for (walk = ped_disk_extended_partition (disk)->part_list; walk;
	     walk = walk->next) {
		PED_ASSERT (walk->type & PED_PARTITION_LOGICAL);
		if (walk->prev)
			PED_ASSERT (walk->prev->next == walk);
	}
	return 1;
}
#endif

/**
 * Returns the partition numbered \p num.
 *
 * \return \c NULL if the specified partition does not exist.
 */
PedPartition*
ped_disk_get_partition (const PedDisk* disk, int num)
{
	PedPartition*	walk;

	PED_ASSERT (disk != NULL);

	for (walk = disk->part_list; walk;
	     walk = ped_disk_next_partition (disk, walk)) {
		if (walk->num == num && !(walk->type & PED_PARTITION_FREESPACE))
			return walk;
	}

	return NULL;
}

/**
 * Returns the partition that contains sect.  If sect lies within a logical
 * partition, then the logical partition is returned (not the extended
 * partition).
 */
PedPartition*
ped_disk_get_partition_by_sector (const PedDisk* disk, PedSector sect)
{
	PedPartition*	walk;

	PED_ASSERT (disk != NULL);

	for (walk = disk->part_list; walk;
	     walk = ped_disk_next_partition (disk, walk)) {
		if (ped_geometry_test_sector_inside (&walk->geom, sect)
		    && walk->type != PED_PARTITION_EXTENDED)
			return walk;
	}

	/* should never get here, unless sect is outside of disk's useable
	 * part, or we're in "update mode", and the free space place-holders
	 * have been removed with _disk_remove_freespace()
	 */
	return NULL;
}

/**
 * Return the maximum representable length (in sectors) of a
 * partition on disk \disk.
 */
PedSector
ped_disk_max_partition_length (const PedDisk* disk)
{
  return disk->type->ops->max_length ();
}

/**
 * Return the maximum representable start sector of a
 * partition on disk \disk.
 */
PedSector
ped_disk_max_partition_start_sector (const PedDisk* disk)
{
  return disk->type->ops->max_start_sector ();
}

/* I'm beginning to agree with Sedgewick :-/ */
static int
_disk_raw_insert_before (PedDisk* disk, PedPartition* loc, PedPartition* part)
{
	PED_ASSERT (disk != NULL);
	PED_ASSERT (loc != NULL);
	PED_ASSERT (part != NULL);

	part->prev = loc->prev;
	part->next = loc;
	if (part->prev) {
		part->prev->next = part;
	} else {
		if (loc->type & PED_PARTITION_LOGICAL)
			ped_disk_extended_partition (disk)->part_list = part;
		else
			disk->part_list = part;
	}
	loc->prev = part;

	return 1;
}

static int
_disk_raw_insert_after (PedDisk* disk, PedPartition* loc, PedPartition* part)
{
	PED_ASSERT (disk != NULL);
	PED_ASSERT (loc != NULL);
	PED_ASSERT (part != NULL);

	part->prev = loc;
	part->next = loc->next;
	if (loc->next)
		loc->next->prev = part;
	loc->next = part;

	return 1;
}

static int
_disk_raw_remove (PedDisk* disk, PedPartition* part)
{
	PED_ASSERT (disk != NULL);
	PED_ASSERT (part != NULL);

	if (part->prev) {
		part->prev->next = part->next;
		if (part->next)
			part->next->prev = part->prev;
	} else {
		if (part->type & PED_PARTITION_LOGICAL) {
			ped_disk_extended_partition (disk)->part_list
				= part->next;
		} else {
			disk->part_list = part->next;
		}
		if (part->next)
			part->next->prev = NULL;
	}

	return 1;
}

/*
 *UPDATE MODE ONLY
 */
static int
_disk_raw_add (PedDisk* disk, PedPartition* part)
{
	PedPartition*	walk;
	PedPartition*	last;
	PedPartition*	ext_part;

	PED_ASSERT (disk->update_mode);

	ext_part = ped_disk_extended_partition (disk);

	last = NULL;
	walk = (part->type & PED_PARTITION_LOGICAL) ?
			ext_part->part_list : disk->part_list;

	for (; walk; last = walk, walk = walk->next) {
		if (walk->geom.start > part->geom.end)
			break;
	}

	if (walk) {
		return _disk_raw_insert_before (disk, walk, part);
	} else {
		if (last) {
			return _disk_raw_insert_after (disk, last, part);
		} else {
			if (part->type & PED_PARTITION_LOGICAL)
				ext_part->part_list = part;
			else
				disk->part_list = part;
		}
	}

	return 1;
}

static PedConstraint*
_partition_get_overlap_constraint (PedPartition* part, PedGeometry* geom)
{
	PedSector	min_start;
	PedSector	max_end;
	PedPartition*	walk;
	PedGeometry	free_space;

	PED_ASSERT (part->disk->update_mode);
	PED_ASSERT (part->geom.dev == geom->dev);

	if (part->type & PED_PARTITION_LOGICAL) {
		PedPartition* ext_part;

		ext_part = ped_disk_extended_partition (part->disk);
		PED_ASSERT (ext_part != NULL);

		min_start = ext_part->geom.start;
		max_end = ext_part->geom.end;
		walk = ext_part->part_list;
	} else {
		min_start = 0;
		max_end = LLONG_MAX - 1;
		walk = part->disk->part_list;
	}

	while (walk != NULL
	       && (walk->geom.start < geom->start
			    || min_start >= walk->geom.start)) {
		if (walk != part)
			min_start = walk->geom.end + 1;
		walk = walk->next;
	}

	if (walk == part)
		walk = walk->next;

	if (walk)
		max_end = walk->geom.start - 1;

	if (min_start > max_end)
		return NULL;

	ped_geometry_init (&free_space, part->disk->dev,
			   min_start, max_end - min_start + 1);
	return ped_constraint_new_from_max (&free_space);
}

static int
_partition_check_basic_sanity (PedDisk* disk, PedPartition* part)
{
	PedPartition*	ext_part = ped_disk_extended_partition (disk);

	PED_ASSERT (part->disk == disk);

	PED_ASSERT (part->geom.start >= 0);
	PED_ASSERT (part->geom.start <= part->geom.end);

	if (!ped_disk_type_check_feature (disk->type, PED_DISK_TYPE_EXTENDED)
	    && (part->type == PED_PARTITION_EXTENDED
		    || part->type == PED_PARTITION_LOGICAL)) {
		ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_CANCEL,
			_("%s disk labels don't support logical or extended "
			  "partitions."),
			disk->type->name);
		return 0;
	}

	if (ped_partition_is_active (part)
			&& ! (part->type & PED_PARTITION_LOGICAL)) {
		if (ped_disk_get_primary_partition_count (disk) + 1
		    > ped_disk_get_max_primary_partition_count (disk)) {
			ped_exception_throw (
				PED_EXCEPTION_ERROR,
				PED_EXCEPTION_CANCEL,
				_("Too many primary partitions."));
			return 0;
		}
	}

	if ((part->type & PED_PARTITION_LOGICAL) && !ext_part) {
		ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_CANCEL,
			_("Can't add a logical partition to %s, because "
			"there is no extended partition."),
			disk->dev->path);
		return 0;
	}

	return 1;
}

static int
_check_extended_partition (PedDisk* disk, PedPartition* part)
{
	PedPartition*		walk;
	PedPartition*		ext_part;

	PED_ASSERT (disk != NULL);
	ext_part = ped_disk_extended_partition (disk);
	if (!ext_part) ext_part = part;
	PED_ASSERT (ext_part != NULL);

	if (part != ext_part) {
		ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_CANCEL,
			_("Can't have more than one extended partition on %s."),
			disk->dev->path);
		return 0;
	}

	for (walk = ext_part->part_list; walk; walk = walk->next) {
		if (!ped_geometry_test_inside (&ext_part->geom, &walk->geom)) {
			ped_exception_throw (
				PED_EXCEPTION_ERROR,
				PED_EXCEPTION_CANCEL,
				_("Can't have logical partitions outside of "
				  "the extended partition."));
			return 0;
		}
	}
	return 1;
}

static int
_check_partition (PedDisk* disk, PedPartition* part)
{
	PedPartition*	ext_part = ped_disk_extended_partition (disk);

	PED_ASSERT (part->geom.start <= part->geom.end);

	if (part->type == PED_PARTITION_EXTENDED) {
		if (!_check_extended_partition (disk, part))
			return 0;
	}

	if (part->type & PED_PARTITION_LOGICAL
	    && !ped_geometry_test_inside (&ext_part->geom, &part->geom)) {
		if (ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_IGNORE_CANCEL,
			_("Can't have a logical partition outside of the "
			  "extended partition on %s."),
			disk->dev->path) != PED_EXCEPTION_IGNORE)
			return 0;
	}

	if (! (part->type & PED_PARTITION_LOGICAL)
	    && ext_part && ext_part != part
	    && ped_geometry_test_inside (&ext_part->geom, &part->geom)) {
		if (ped_exception_throw (PED_EXCEPTION_ERROR, PED_EXCEPTION_IGNORE_CANCEL,
			_("Can't have a primary partition inside an extended "
			  "partition.")) != PED_EXCEPTION_IGNORE)
			return 0;
	}

	if (part->geom.end >= disk->dev->length) {
		if (ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_IGNORE_CANCEL,
			_("Can't have a partition outside the disk!"))
		    != PED_EXCEPTION_IGNORE )
		return 0;
	}

	if (!(part->type & PED_PARTITION_METADATA))
		if (!disk->type->ops->partition_check(part))
			return 0;

	return 1;
}

/**
 * Adds PedPartition \p part to PedDisk \p disk.
 *
 * \warning The partition's geometry may be changed, subject to \p constraint.
 * You could set \p constraint to <tt>ped_constraint_exact(&part->geom)</tt>,
 * but many partition table schemes have special requirements on the start
 * and end of partitions.  Therefore, having an overly strict constraint
 * will probably mean that this function will fail (in which
 * case \p part will be left unmodified)
 * \p part is assigned a number (\p part->num) in this process.
 *
 * \return \c 0 on failure.
 */
int
ped_disk_add_partition (PedDisk* disk, PedPartition* part,
			const PedConstraint* constraint)
{
	PedConstraint*	overlap_constraint = NULL;
	PedConstraint*	constraints = NULL;

	PED_ASSERT (disk != NULL);
	PED_ASSERT (part != NULL);

	if (!_partition_check_basic_sanity (disk, part))
		return 0;

	if (!_disk_push_update_mode (disk))
		return 0;

	if (ped_partition_is_active (part)) {
		overlap_constraint
			= _partition_get_overlap_constraint (part, &part->geom);
		constraints = ped_constraint_intersect (overlap_constraint,
							constraint);

		if (!constraints && constraint) {
			if (ped_exception_throw (
				PED_EXCEPTION_ERROR,
				PED_EXCEPTION_IGNORE_CANCEL,
				_("Can't have overlapping partitions.")) != PED_EXCEPTION_IGNORE)
			goto error;
		} else constraint = constraints;
		if (!_partition_enumerate (part))
			goto error;
		if (!_partition_align (part, constraint))
			goto error;
	}
        /* FIXME: when _check_partition fails, we end up leaking PART
           at least for DVH partition tables.  Simply calling
           ped_partition_destroy(part) here fixes it for DVH, but
           causes trouble for other partition types.  Similarly,
           reordering these two checks, putting _check_partition after
           _disk_raw_add induces an infinite loop.  */
	if (!_check_partition (disk, part))
		goto error;
	if (!_disk_raw_add (disk, part))
		goto error;

	ped_constraint_destroy (overlap_constraint);
	ped_constraint_destroy (constraints);
	if (!_disk_pop_update_mode (disk))
		return 0;
#ifdef DEBUG
	if (!_disk_check_sanity (disk))
		return 0;
#endif
	return 1;

error:
	ped_constraint_destroy (overlap_constraint);
	ped_constraint_destroy (constraints);
	_disk_pop_update_mode (disk);
	return 0;
}

/**
 * Removes PedPartition \p part from PedDisk \p disk.
 *
 * If \p part is an extended partition, it must not contain any logical
 * partitions. \p part is *NOT* destroyed. The caller must call
 * ped_partition_destroy(), or use ped_disk_delete_partition() instead.
 *
 * \return \c 0 on error.
 */
int
ped_disk_remove_partition (PedDisk* disk, PedPartition* part)
{
	PED_ASSERT (disk != NULL);
	PED_ASSERT (part != NULL);

	if (!_disk_push_update_mode (disk))
		return 0;
	PED_ASSERT (part->part_list == NULL);
	_disk_raw_remove (disk, part);
	if (!_disk_pop_update_mode (disk))
		return 0;
	ped_disk_enumerate_partitions (disk);
	return 1;
}

static int
ped_disk_delete_all_logical (PedDisk* disk);

/**
 * Removes \p part from \p disk, and destroys \p part.
 *
 * \return \c 0 on failure.
 */
int
ped_disk_delete_partition (PedDisk* disk, PedPartition* part)
{
	PED_ASSERT (disk != NULL);
	PED_ASSERT (part != NULL);

	if (!_disk_push_update_mode (disk))
		return 0;
	if (part->type == PED_PARTITION_EXTENDED)
		ped_disk_delete_all_logical (disk);
	ped_disk_remove_partition (disk, part);
	ped_partition_destroy (part);
	if (!_disk_pop_update_mode (disk))
		return 0;

	return 1;
}

static int
ped_disk_delete_all_logical (PedDisk* disk)
{
	PedPartition*		walk;
	PedPartition*		next;
	PedPartition*		ext_part;

	PED_ASSERT (disk != NULL);
	ext_part = ped_disk_extended_partition (disk);
	PED_ASSERT (ext_part != NULL);

	for (walk = ext_part->part_list; walk; walk = next) {
		next = walk->next;

		if (!ped_disk_delete_partition (disk, walk))
			return 0;
	}
	return 1;
}

/**
 * Removes and destroys all partitions on \p disk.
 *
 * \return \c 0 on failure.
 */
int
ped_disk_delete_all (PedDisk* disk)
{
	PedPartition*		walk;
	PedPartition*		next;

	PED_ASSERT (disk != NULL);

	if (!_disk_push_update_mode (disk))
		return 0;

	for (walk = disk->part_list; walk; walk = next) {
		next = walk->next;

		if (!ped_disk_delete_partition (disk, walk)) {
		        _disk_pop_update_mode(disk);
			return 0;
                }
	}

	if (!_disk_pop_update_mode (disk))
		return 0;

	return 1;
}

/**
 * Sets the geometry of \p part (i.e. change a partitions location). This can
 * fail for many reasons, e.g. can't overlap with other partitions. If it
 * does fail, \p part will remain unchanged. Returns \c 0 on failure. \p part's
 * geometry may be set to something different from \p start and \p end subject
 * to \p constraint.
 *
 * \warning The constraint warning from ped_disk_add_partition() applies.
 *
 * \note this function does not modify the contents of the partition.  You need
 *       to call ped_file_system_resize() separately.
 */
int
ped_disk_set_partition_geom (PedDisk* disk, PedPartition* part,
			     const PedConstraint* constraint,
			     PedSector start, PedSector end)
{
	PedConstraint*	overlap_constraint = NULL;
	PedConstraint*	constraints = NULL;
	PedGeometry	old_geom;
	PedGeometry	new_geom;

	PED_ASSERT (disk != NULL);
	PED_ASSERT (part != NULL);
	PED_ASSERT (part->disk == disk);

	old_geom = part->geom;
	if (!ped_geometry_init (&new_geom, part->geom.dev, start, end - start + 1))
		return 0;

	if (!_disk_push_update_mode (disk))
		return 0;

	overlap_constraint
		= _partition_get_overlap_constraint (part, &new_geom);
	constraints = ped_constraint_intersect (overlap_constraint, constraint);
	if (!constraints && constraint) {
		ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_CANCEL,
			_("Can't have overlapping partitions."));
		goto error_pop_update_mode;
	}

	part->geom = new_geom;
	if (!_partition_align (part, constraints))
		goto error_pop_update_mode;
	if (!_check_partition (disk, part))
		goto error_pop_update_mode;

	/* remove and add, to ensure the ordering gets updated if necessary */
	_disk_raw_remove (disk, part);
	_disk_raw_add (disk, part);

	if (!_disk_pop_update_mode (disk))
		goto error;

	ped_constraint_destroy (overlap_constraint);
	ped_constraint_destroy (constraints);
	return 1;

error_pop_update_mode:
	_disk_pop_update_mode (disk);
error:
	ped_constraint_destroy (overlap_constraint);
	ped_constraint_destroy (constraints);
	part->geom = old_geom;
	return 0;
}

/**
 * Grow PedPartition \p part geometry to the maximum possible subject to
 * \p constraint.  The new geometry will be a superset of the old geometry.
 *
 * \return 0 on failure
 */
int
ped_disk_maximize_partition (PedDisk* disk, PedPartition* part,
			     const PedConstraint* constraint)
{
	PedGeometry	old_geom;
	PedSector	global_min_start;
	PedSector	global_max_end;
	PedSector	new_start;
	PedSector	new_end;
	PedPartition*	ext_part = ped_disk_extended_partition (disk);
	PedConstraint*	constraint_any;

	PED_ASSERT (disk != NULL);
	PED_ASSERT (part != NULL);

	if (part->type & PED_PARTITION_LOGICAL) {
		PED_ASSERT (ext_part != NULL);
		global_min_start = ext_part->geom.start;
		global_max_end = ext_part->geom.end;
	} else {
		global_min_start = 0;
		global_max_end = disk->dev->length - 1;
	}

	old_geom = part->geom;

	if (!_disk_push_update_mode (disk))
		return 0;

	if (part->prev)
		new_start = part->prev->geom.end + 1;
	else
		new_start = global_min_start;

	if (part->next)
		new_end = part->next->geom.start - 1;
	else
		new_end = global_max_end;

	if (!ped_disk_set_partition_geom (disk, part, constraint, new_start,
					  new_end))
		goto error;

	if (!_disk_pop_update_mode (disk))
		return 0;
	return 1;

error:
	constraint_any = ped_constraint_any (disk->dev);
	ped_disk_set_partition_geom (disk, part, constraint_any,
				     old_geom.start, old_geom.end);
	ped_constraint_destroy (constraint_any);
	_disk_pop_update_mode (disk);
	return 0;
}

/**
 * Get the maximum geometry \p part can be grown to, subject to
 * \p constraint.
 *
 * \return \c NULL on failure.
 */
PedGeometry*
ped_disk_get_max_partition_geometry (PedDisk* disk, PedPartition* part,
				     const PedConstraint* constraint)
{
	PedGeometry	old_geom;
	PedGeometry*	max_geom;
	PedConstraint*	constraint_exact;

	PED_ASSERT(disk != NULL);
	PED_ASSERT(part != NULL);
	PED_ASSERT(ped_partition_is_active (part));

	old_geom = part->geom;
	if (!ped_disk_maximize_partition (disk, part, constraint))
		return NULL;
	max_geom = ped_geometry_duplicate (&part->geom);

	constraint_exact = ped_constraint_exact (&old_geom);
	ped_disk_set_partition_geom (disk, part, constraint_exact,
				     old_geom.start, old_geom.end);
	ped_constraint_destroy (constraint_exact);

	/* this assertion should never fail, because the old
	 * geometry was valid
	 */
	PED_ASSERT (ped_geometry_test_equal (&part->geom, &old_geom));

	return max_geom;
}

/**
 * Reduce the size of the extended partition to a minimum while still wrapping
 * its logical partitions.  If there are no logical partitions, remove the
 * extended partition.
 *
 * \return 0 on failure.
 */
int
ped_disk_minimize_extended_partition (PedDisk* disk)
{
	PedPartition*		first_logical;
	PedPartition*		last_logical;
	PedPartition*		walk;
	PedPartition*		ext_part;
	PedConstraint*		constraint;
	int			status;

	PED_ASSERT (disk != NULL);

	ext_part = ped_disk_extended_partition (disk);
	if (!ext_part)
		return 1;

	if (!_disk_push_update_mode (disk))
		return 0;

	first_logical = ext_part->part_list;
	if (!first_logical) {
		if (!_disk_pop_update_mode (disk))
			return 0;
		return ped_disk_delete_partition (disk, ext_part);
	}

	for (walk = first_logical; walk->next; walk = walk->next);
	last_logical = walk;

	constraint = ped_constraint_any (disk->dev);
	status = ped_disk_set_partition_geom (disk, ext_part, constraint,
					      first_logical->geom.start,
					      last_logical->geom.end);
	ped_constraint_destroy (constraint);

	if (!_disk_pop_update_mode (disk))
		return 0;
	return status;
}

/**
 * @}
 */

/**
 * \addtogroup PedPartition
 *
 * @{
 */

/**
 * Returns a name that seems mildly appropriate for a partition type \p type.
 *
 * Eg, if you pass (PED_PARTITION_LOGICAL & PED_PARTITION_FREESPACE), it
 * will return "free".  This isn't to be taken too seriously - it's just
 * useful for user interfaces, so you can show the user something ;-)
 *
 * \note The returned string will be in English.  However,
 * translations are provided, so the caller can call
 * dgettext("parted", RESULT) on the result.
 *
 */
const char*
ped_partition_type_get_name (PedPartitionType type)
{
	if (type & PED_PARTITION_METADATA)
		return N_("metadata");
	else if (type & PED_PARTITION_FREESPACE)
		return N_("free");
	else if (type & PED_PARTITION_EXTENDED)
		return N_("extended");
	else if (type & PED_PARTITION_LOGICAL)
		return N_("logical");
	else
		return N_("primary");
}


/**
 * Returns a name for a \p flag, e.g. PED_PARTITION_BOOT will return "boot".
 *
 * \note The returned string will be in English.  However,
 * translations are provided, so the caller can call
 * dgettext("parted", RESULT) on the result.
 */
const char*
ped_partition_flag_get_name (PedPartitionFlag flag)
{
	switch (flag) {
	case PED_PARTITION_BOOT:
		return N_("boot");
	case PED_PARTITION_BIOS_GRUB:
		return N_("bios_grub");
	case PED_PARTITION_ROOT:
		return N_("root");
	case PED_PARTITION_SWAP:
		return N_("swap");
	case PED_PARTITION_HIDDEN:
		return N_("hidden");
	case PED_PARTITION_RAID:
		return N_("raid");
	case PED_PARTITION_LVM:
		return N_("lvm");
	case PED_PARTITION_LBA:
		return N_("lba");
	case PED_PARTITION_HPSERVICE:
		return N_("hp-service");
	case PED_PARTITION_PALO:
		return N_("palo");
	case PED_PARTITION_PREP:
		return N_("prep");
	case PED_PARTITION_MSFT_RESERVED:
		return N_("msftres");
	case PED_PARTITION_MSFT_DATA:
		return N_("msftdata");
        case PED_PARTITION_APPLE_TV_RECOVERY:
                return N_("atvrecv");
        case PED_PARTITION_DIAG:
                return N_("diag");
        case PED_PARTITION_LEGACY_BOOT:
                return N_("legacy_boot");
        case PED_PARTITION_IRST:
                return N_("irst");
        case PED_PARTITION_ESP:
                return N_("esp");
        case PED_PARTITION_CHROMEOS_KERNEL:
                return N_("chromeos_kernel");
	case PED_PARTITION_BLS_BOOT:
		return N_("bls_boot");
        case PED_PARTITION_LINUX_HOME:
                return N_("linux-home");
        case PED_PARTITION_NO_AUTOMOUNT:
                return N_("no_automount");

	default:
		ped_exception_throw (
			PED_EXCEPTION_BUG,
			PED_EXCEPTION_CANCEL,
			_("Unknown partition flag, %d."),
			flag);
		return NULL;
	}
}

/**
 * Iterates through all flags.
 *
 * ped_partition_flag_next(0) returns the first flag
 *
 * \return the next flag, or 0 if there are no more flags
 */
PedPartitionFlag
ped_partition_flag_next (PedPartitionFlag flag)
{
	return (flag + 1) % (PED_PARTITION_LAST_FLAG + 1);
}

/**
 * Returns the flag associated with \p name.
 *
 * \p name can be the English
 * string, or the translation for the native language.
 */
PedPartitionFlag
ped_partition_flag_get_by_name (const char* name)
{
	PedPartitionFlag	flag;
	const char*		flag_name;

	for (flag = ped_partition_flag_next (0); flag;
	     		flag = ped_partition_flag_next (flag)) {
		flag_name = ped_partition_flag_get_name (flag);
		if (flag_name && (strcasecmp (name, flag_name) == 0
		    || strcasecmp (name, _(flag_name)) == 0))
			return flag;
	}

	return 0;
}

static void
ped_partition_print (const PedPartition* part)
{
	PED_ASSERT (part != NULL);

	printf ("  %-10s %02d  (%d->%d)\n",
		ped_partition_type_get_name (part->type),
		part->num,
		(int) part->geom.start, (int) part->geom.end);
}

/** @} */

/**
 * \addtogroup PedDisk
 *
 * @{
 */

/**
 * Prints a summary of disk's partitions.  Useful for debugging.
 */
void
ped_disk_print (const PedDisk* disk)
{
	PedPartition*	part;

	PED_ASSERT (disk != NULL);

	for (part = disk->part_list; part;
	     part = ped_disk_next_partition (disk, part))
		ped_partition_print (part);
}

/** @} */
