/* -*- Mode: c; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-

    libparted - a library for manipulating disk partitions
    Copyright (C) 2000-2001, 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/>.

    Contributor:  Phil Knirsch <phil@redhat.de>
                  Harald Hoyer <harald@redhat.de>
*/

#include <config.h>

#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <time.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdbool.h>

#include <sys/stat.h>
#include <sys/ioctl.h>
#include <parted/parted.h>
#include <parted/endian.h>
#include <parted/debug.h>

#include <parted/vtoc.h>
#include <parted/fdasd.h>
#include <arch/linux.h>

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

#include "misc.h"
#include "pt-tools.h"

#define PARTITION_LINUX_SWAP 0x82
#define PARTITION_LINUX 0x83
#define PARTITION_LINUX_LVM 0x8e
#define PARTITION_LINUX_RAID 0xfd

extern void ped_disk_dasd_init ();
extern void ped_disk_dasd_done ();

#define DASD_NAME "dasd"

typedef struct {
	int type;
	int system;
} DasdPartitionData;

typedef struct {
	unsigned int format_type;
	unsigned int label_block;
	volume_label_t vlabel;
} DasdDiskSpecific;

static int dasd_probe (const PedDevice *dev);
static int dasd_read (PedDisk* disk);
static int dasd_write (const PedDisk* disk);

static PedPartition* dasd_partition_new (const PedDisk* disk,
										 PedPartitionType part_type,
										 const PedFileSystemType* fs_type,
										 PedSector start,
										 PedSector end);
static PedPartition* dasd_partition_duplicate (const PedPartition *part);
static void dasd_partition_destroy (PedPartition* part);
static int dasd_partition_set_flag (PedPartition* part,
									PedPartitionFlag flag,
									int state);
static int dasd_partition_get_flag (const PedPartition* part,
									PedPartitionFlag flag);
static int dasd_partition_is_flag_available (const PedPartition* part,
											 PedPartitionFlag flag);
static int dasd_partition_align (PedPartition* part,
								 const PedConstraint* constraint);
static int dasd_partition_enumerate (PedPartition* part);
static int dasd_get_max_primary_partition_count (const PedDisk* disk);
static bool dasd_get_max_supported_partition_count (const PedDisk* disk, int *max_n);
static PedAlignment *dasd_get_partition_alignment(const PedDisk *disk);

static PedDisk* dasd_alloc (const PedDevice* dev);
static PedDisk* dasd_duplicate (const PedDisk* disk);
static void dasd_free (PedDisk* disk);
static int dasd_partition_set_system (PedPartition* part,
									  const PedFileSystemType* fs_type);
static int dasd_alloc_metadata (PedDisk* disk);

#include "pt-common.h"
PT_define_limit_functions (dasd)

static PedDiskOps dasd_disk_ops = {
	clobber:		NULL,
	write:			NULL_IF_DISCOVER_ONLY (dasd_write),

	partition_set_name:	NULL,
	partition_get_name:	NULL,
	partition_set_type_id:          NULL,
	partition_get_type_id:          NULL,
	partition_set_type_uuid:	NULL,
	partition_get_type_uuid:	NULL,

	get_partition_alignment: dasd_get_partition_alignment,

	PT_op_function_initializers (dasd)
};

static PedDiskType dasd_disk_type = {
	next: NULL,
	name: "dasd",
	ops: &dasd_disk_ops,
	features: 0
};

struct flag_id_mapping_t
{
	enum _PedPartitionFlag flag;
	int type_id;
};

static const struct flag_id_mapping_t flag_id_mapping[] =
{
	{ PED_PARTITION_LVM,	PARTITION_LINUX_LVM },
	{ PED_PARTITION_RAID,	PARTITION_LINUX_RAID },
	{ PED_PARTITION_SWAP,	PARTITION_LINUX_SWAP },
};

static const struct flag_id_mapping_t* _GL_ATTRIBUTE_CONST
dasd_find_flag_id_mapping (PedPartitionFlag flag)
{
	int n = sizeof(flag_id_mapping) / sizeof(flag_id_mapping[0]);

	for (int i = 0; i < n; ++i)
		if (flag_id_mapping[i].flag == flag)
			return &flag_id_mapping[i];

	return NULL;
}

static PedDisk*
dasd_alloc (const PedDevice* dev)
{
	PedDisk* disk;
	LinuxSpecific* arch_specific;
	DasdDiskSpecific *disk_specific;
	char volser[7];

	PED_ASSERT (dev != NULL);

	arch_specific = LINUX_SPECIFIC (dev);
	disk = _ped_disk_alloc (dev, &dasd_disk_type);
	if (!disk)
		return NULL;

	disk->disk_specific = disk_specific = ped_malloc(sizeof(DasdDiskSpecific));
	if (!disk->disk_specific) {
		free (disk);
		return NULL;
	}

	/* CDL format, newer */
	disk_specific->format_type = 2;
	disk_specific->label_block = 2;

	/* Setup volume label (for fresh disks) */
	snprintf(volser, sizeof(volser), "0X%04X", arch_specific->devno);
	vtoc_volume_label_init(&disk_specific->vlabel);
	vtoc_volume_label_set_key(&disk_specific->vlabel, "VOL1");
	vtoc_volume_label_set_label(&disk_specific->vlabel, "VOL1");
	vtoc_volume_label_set_volser(&disk_specific->vlabel, volser);
	vtoc_set_cchhb(&disk_specific->vlabel.vtoc,
		       VTOC_START_CC, VTOC_START_HH, 0x01);

	return disk;
}

static PedDisk*
dasd_duplicate (const PedDisk* disk)
{
	PedDisk* new_disk;

	new_disk = ped_disk_new_fresh(disk->dev, &dasd_disk_type);

	if (!new_disk)
		return NULL;

	memcpy(new_disk->disk_specific, disk->disk_specific,
	       sizeof(DasdDiskSpecific));

	return new_disk;
}

static void
dasd_free (PedDisk* disk)
{
	PED_ASSERT(disk != NULL);
	/* Don't free disk->disk_specific first, in case _ped_disk_free
	   or one of its eventual callees ever accesses it.  */
	void *p = disk->disk_specific;
	_ped_disk_free(disk);
	free(p);
}


void
ped_disk_dasd_init ()
{
	ped_disk_type_register(&dasd_disk_type);
}

void
ped_disk_dasd_done ()
{
	ped_disk_type_unregister(&dasd_disk_type);
}

static int
dasd_probe (const PedDevice *dev)
{
	LinuxSpecific* arch_specific;
	struct fdasd_anchor anchor;

	PED_ASSERT(dev != NULL);

	arch_specific = LINUX_SPECIFIC(dev);

	/* add partition test here */
	fdasd_initialize_anchor(&anchor);

	if (fdasd_get_geometry(dev, &anchor, arch_specific->fd) == 0)
                goto error_cleanup;

	/* Labels are required on CDL formatted DASDs. */
	if (fdasd_check_volume(&anchor, arch_specific->fd) &&
	    anchor.FBA_layout == 0)
		goto error_cleanup;

	fdasd_cleanup(&anchor);

	return 1;

 error_cleanup:
	fdasd_cleanup(&anchor);
	ped_exception_throw(PED_EXCEPTION_ERROR,PED_EXCEPTION_IGNORE_CANCEL,
			    "Error while probing device %s.", dev->path);

	return 0;
}

static int
dasd_read (PedDisk* disk)
{
	int i;
	char str[20];
	PedDevice* dev;
	PedPartition* part;
	PedFileSystemType *fs;
	PedSector start, end;
	PedConstraint* constraint_exact;
	partition_info_t *p;
	LinuxSpecific* arch_specific;
	DasdDiskSpecific* disk_specific;
	struct fdasd_anchor anchor;

	PDEBUG;

	PED_ASSERT (disk != NULL);
	PDEBUG;
	PED_ASSERT (disk->dev != NULL);
	PDEBUG;

	dev = disk->dev;

	arch_specific = LINUX_SPECIFIC(dev);
	disk_specific = disk->disk_specific;

	PDEBUG;

	fdasd_initialize_anchor(&anchor);

	if (fdasd_get_geometry(disk->dev, &anchor, arch_specific->fd) == 0)
                goto error_close_dev;

	disk_specific->label_block = anchor.label_block;

	if ((anchor.geo.cylinders * anchor.geo.heads) > BIG_DISK_SIZE)
		anchor.big_disk++;

	/* check dasd for labels and vtoc */
	if (fdasd_check_volume(&anchor, arch_specific->fd)) {
		DasdPartitionData* dasd_data;

		/* Kernel partitioning code will report 'implicit' partitions
		 * for non-CDL format DASDs even when there is no
		 * label/VTOC.  */
		if (anchor.FBA_layout == 0)
			goto error_close_dev;

		disk_specific->format_type = 1;

		/* Register implicit partition */
		ped_disk_delete_all (disk);

		start = (PedSector) arch_specific->real_sector_size /
			(PedSector) disk->dev->sector_size *
			(PedSector) (anchor.label_block + 1);
		end = disk->dev->length - 1;
		part = ped_partition_new (disk, PED_PARTITION_NORMAL, NULL,
					  start, end);
		if (!part)
			goto error_close_dev;

		part->num = 1;
		part->fs_type = ped_file_system_probe (&part->geom);
		dasd_data = part->disk_specific;
		dasd_data->type = 0;

		if (!ped_disk_add_partition (disk, part, NULL))
			goto error_close_dev;

		fdasd_cleanup(&anchor);

		return 1;
	}

	/* Save volume label (read by fdasd_check_volume) for writing */
	memcpy(&disk_specific->vlabel, anchor.vlabel, sizeof(volume_label_t));

	ped_disk_delete_all (disk);

	bool is_ldl = strncmp(anchor.vlabel->volkey,
			 vtoc_ebcdic_enc("LNX1", str, 4), 4) == 0;
	bool is_cms = strncmp(anchor.vlabel->volkey,
			 vtoc_ebcdic_enc("CMS1", str, 4), 4) == 0;
	if (is_ldl || is_cms) {
		DasdPartitionData* dasd_data;

		union vollabel {
			volume_label_t ldl;
			cms_volume_label_t cms;
		};
		union vollabel *cms_ptr1 = (union vollabel *) anchor.vlabel;
		cms_volume_label_t *cms_ptr = &cms_ptr1->cms;
		volume_label_t *ldl_ptr = &cms_ptr1->ldl;
		int partition_start_block;

		disk_specific->format_type = 1;

		if (is_cms && cms_ptr->usable_count >= cms_ptr->block_count)
			partition_start_block = 2;   /* FBA DASD */
		else
			partition_start_block = 3;   /* CKD DASD */

		if (is_ldl)
			start = (long long) arch_specific->real_sector_size
				/ (long long) disk->dev->sector_size
				* (long long) partition_start_block;
		else if (cms_ptr->disk_offset == 0)
			start = (long long) cms_ptr->block_size
				/ (long long) disk->dev->sector_size
				* (long long) partition_start_block;
		else
			start = (long long) cms_ptr->block_size
				/ (long long) disk->dev->sector_size
				* (long long) cms_ptr->disk_offset;

		if (is_ldl)
		   if (ldl_ptr->ldl_version >= 0xf2)
		      end = (long long) arch_specific->real_sector_size
			    / (long long) disk->dev->sector_size
			    * (long long) ldl_ptr->formatted_blocks - 1;
		   else
		      end = disk->dev->length - 1;
		else
		   if (cms_ptr->disk_offset == 0)
		      end = (long long) cms_ptr->block_size
			    / (long long) disk->dev->sector_size
			    * (long long) cms_ptr->block_count - 1;
		   else
		      /*
			 Frankly, I do not understand why the last block
			 of the CMS reserved file is not included in the
			 partition; but this is the algorithm used by the
			 Linux kernel.  See fs/partitions/ibm.c in the
			 Linux kernel source code.
		      */
		      end = (long long) cms_ptr->block_size
			    / (long long) disk->dev->sector_size
			    * (long long) (cms_ptr->block_count - 1) - 1;

		part = ped_partition_new (disk, PED_PARTITION_NORMAL, NULL, start, end);
		if (!part)
			goto error_close_dev;

		part->num = 1;
		part->fs_type = ped_file_system_probe (&part->geom);
		dasd_data = part->disk_specific;
		dasd_data->type = 0;

		if (!ped_disk_add_partition (disk, part, NULL))
			goto error_close_dev;

		fdasd_cleanup(&anchor);

		return 1;
	}

	/* CDL format, newer */
	disk_specific->format_type = 2;

	p = anchor.first;
	PDEBUG;

	for (i = 1 ; i <= USABLE_PARTITIONS; i++) {
		char *ch = p->f1->DS1DSNAM;
		DasdPartitionData* dasd_data;


		if (p->used != 0x01)
			continue;

        PDEBUG;

		start = (long long)(long long) p->start_trk
				* (long long) disk->dev->hw_geom.sectors
				* (long long) arch_specific->real_sector_size
				/ (long long) disk->dev->sector_size;
		end   = (long long)((long long) p->end_trk + 1)
				* (long long) disk->dev->hw_geom.sectors
				* (long long) arch_specific->real_sector_size
				/ (long long) disk->dev->sector_size - 1;
		part = ped_partition_new(disk, PED_PARTITION_NORMAL, NULL,
                                         start, end);
        PDEBUG;

		if (!part)
			goto error_close_dev;

        PDEBUG;

		part->num = i;
		part->fs_type = ped_file_system_probe(&part->geom);

		vtoc_ebcdic_dec(p->f1->DS1DSNAM, p->f1->DS1DSNAM, 44);
		ch = strstr(p->f1->DS1DSNAM, "PART");

		if (ch != NULL) {
			strncpy(str, ch+9, 6);
			str[6] = '\0';
		}

		dasd_data = part->disk_specific;

		if (strncmp(PART_TYPE_RAID, str, 6) == 0)
			dasd_data->system = PARTITION_LINUX_RAID;
		else if (strncmp(PART_TYPE_LVM, str, 6) == 0)
			dasd_data->system = PARTITION_LINUX_LVM;
		else if (strncmp(PART_TYPE_SWAP, str, 6) == 0)
			dasd_data->system = PARTITION_LINUX_SWAP;

		vtoc_ebcdic_enc(p->f1->DS1DSNAM, p->f1->DS1DSNAM, 44);

		dasd_data->type = 0;

		constraint_exact = ped_constraint_exact (&part->geom);
		if (!constraint_exact)
			goto error_close_dev;
		if (!ped_disk_add_partition(disk, part, constraint_exact)) {
			ped_constraint_destroy(constraint_exact);
			goto error_close_dev;
		}
		ped_constraint_destroy(constraint_exact);

		if (p->fspace_trk > 0) {
			start = (long long)((long long) p->end_trk + 1)
					* (long long) disk->dev->hw_geom.sectors
					* (long long) arch_specific->real_sector_size
					/ (long long) disk->dev->sector_size;
			end   = (long long)((long long) p->end_trk + 1 + p->fspace_trk)
					* (long long) disk->dev->hw_geom.sectors
					* (long long) arch_specific->real_sector_size
					/ (long long) disk->dev->sector_size - 1;
			part = ped_partition_new (disk, PED_PARTITION_NORMAL,
                                                  NULL, start, end);

			if (!part)
				goto error_close_dev;

			part->type = PED_PARTITION_FREESPACE;
			constraint_exact = ped_constraint_exact(&part->geom);

			if (!constraint_exact)
				goto error_close_dev;
			if (!ped_disk_add_partition(disk, part, constraint_exact)) {
				ped_constraint_destroy(constraint_exact);
				goto error_close_dev;
			}

			ped_constraint_destroy (constraint_exact);
		}

		p = p->next;
	}

	PDEBUG;
	fdasd_cleanup(&anchor);
	return 1;

error_close_dev:
	PDEBUG;
	fdasd_cleanup(&anchor);
	return 0;
}

static int
dasd_update_type (const PedDisk* disk, struct fdasd_anchor *anchor,
		  partition_info_t *part_info[USABLE_PARTITIONS])
{
	PedPartition* part;
	LinuxSpecific* arch_specific;
	DasdDiskSpecific* disk_specific;

	arch_specific = LINUX_SPECIFIC(disk->dev);
	disk_specific = disk->disk_specific;

	PDEBUG;

	unsigned int i;
	for (i = 1; i <= USABLE_PARTITIONS; i++) {
		partition_info_t *p;
		char *ch = NULL;
		DasdPartitionData* dasd_data;

		PDEBUG;

		part = ped_disk_get_partition(disk, i);
		if (!part)
			continue;

		PDEBUG;

		dasd_data = part->disk_specific;
		p = part_info[i - 1];

		if (!p ) {
			PDEBUG;
			continue;
		}

		vtoc_ebcdic_dec(p->f1->DS1DSNAM, p->f1->DS1DSNAM, 44);
		ch = strstr(p->f1->DS1DSNAM, "PART");

		PDEBUG;
		if (ch == NULL) {
			vtoc_ebcdic_enc(p->f1->DS1DSNAM, p->f1->DS1DSNAM, 44);
			PDEBUG;
			continue;
		}

		ch += 9;

		switch (dasd_data->system) {
			case PARTITION_LINUX_LVM:
				PDEBUG;
				strncpy(ch, PART_TYPE_LVM, 6);
				break;
			case PARTITION_LINUX_RAID:
				PDEBUG;
				strncpy(ch, PART_TYPE_RAID, 6);
				break;
			case PARTITION_LINUX:
				PDEBUG;
				strncpy(ch, PART_TYPE_NATIVE, 6);
				break;
			case PARTITION_LINUX_SWAP:
				PDEBUG;
				strncpy(ch, PART_TYPE_SWAP, 6);
				break;
			default:
				PDEBUG;
				strncpy(ch, PART_TYPE_NATIVE, 6);
				break;
		}

		anchor->vtoc_changed++;
		vtoc_ebcdic_enc(p->f1->DS1DSNAM, p->f1->DS1DSNAM, 44);
	}

	return 1;
}

static int
dasd_write (const PedDisk* disk)
{
	DasdPartitionData* dasd_data;
	PedPartition* part;
	int i;
	partition_info_t *p;
	LinuxSpecific* arch_specific;
	DasdDiskSpecific* disk_specific;
	struct fdasd_anchor anchor;
	partition_info_t *part_info[USABLE_PARTITIONS];

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

	arch_specific = LINUX_SPECIFIC (disk->dev);
	disk_specific = disk->disk_specific;

	PDEBUG;

	/* If not formated in CDL, don't write anything. */
	if (disk_specific->format_type == 1) {
		ped_exception_throw (PED_EXCEPTION_ERROR,
				     PED_EXCEPTION_CANCEL,
				     _("The partition table of DASD-LDL device cannot be changed.\n"));
		return 1;
	}

	/* initialize the anchor */
	fdasd_initialize_anchor(&anchor);
	if (fdasd_get_geometry(disk->dev, &anchor, arch_specific->fd) == 0)
                goto error;

	fdasd_check_volume(&anchor, arch_specific->fd);
	memcpy(anchor.vlabel, &disk_specific->vlabel, sizeof(volume_label_t));
	anchor.vlabel_changed++;

	if ((anchor.geo.cylinders * anchor.geo.heads) > BIG_DISK_SIZE)
		anchor.big_disk++;

	fdasd_recreate_vtoc(&anchor);

	for (i = 1; i <= USABLE_PARTITIONS; i++) {
		unsigned int start, stop;

		PDEBUG;
		part = ped_disk_get_partition(disk, i);
		if (!part)
			continue;

		PDEBUG;

		start = part->geom.start * disk->dev->sector_size
				/ arch_specific->real_sector_size / disk->dev->hw_geom.sectors;
		stop = (part->geom.end + 1)
			   * disk->dev->sector_size / arch_specific->real_sector_size
			   / disk->dev->hw_geom.sectors - 1;

		PDEBUG;
		dasd_data = part->disk_specific;

		p = fdasd_add_partition(&anchor, start, stop);
		if (!p) {
			PDEBUG;
			goto error;
		}
		part_info[i - 1] = p;
		p->type = dasd_data->system;
	}

	PDEBUG;

	if (!fdasd_prepare_labels(&anchor, arch_specific->fd))
		goto error;

	dasd_update_type(disk, &anchor, part_info);
	PDEBUG;

	if (!fdasd_write_labels(&anchor, arch_specific->fd))
		goto error;

	fdasd_cleanup(&anchor);
	return 1;

error:
	PDEBUG;
	fdasd_cleanup(&anchor);
	return 0;
}

static PedPartition*
dasd_partition_new (const PedDisk* disk, PedPartitionType part_type,
                    const PedFileSystemType* fs_type,
                    PedSector start, PedSector end)
{
	PedPartition* part;

	part = _ped_partition_alloc(disk, part_type, fs_type, start, end);
	if (!part)
		goto error;

	part->disk_specific = ped_calloc (sizeof (DasdPartitionData));
	return part;

error:
	return 0;
}

static PedPartition*
dasd_partition_duplicate (const PedPartition *part)
{
	PedPartition *new_part;

	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;

	memcpy(new_part->disk_specific, part->disk_specific,
	       sizeof(DasdPartitionData));

	return new_part;
}

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

	if (ped_partition_is_active(part))
		free(part->disk_specific);
	free(part);
}

static int
dasd_partition_set_flag (PedPartition* part, PedPartitionFlag flag, int state)
{
	DasdPartitionData* dasd_data;

	PED_ASSERT(part != NULL);
	PED_ASSERT(part->disk_specific != NULL);
	dasd_data = part->disk_specific;

	const struct flag_id_mapping_t* p = dasd_find_flag_id_mapping (flag);
	if (p)
	{
		if (state)
			dasd_data->system = p->type_id;
		else if (dasd_data->system == p->type_id)
			return dasd_partition_set_system (part, part->fs_type);
		return 1;
	}

	return 0;
}

static int
dasd_partition_get_flag (const PedPartition* part, PedPartitionFlag flag)
{
	DasdPartitionData* dasd_data;

	PED_ASSERT (part != NULL);
	PED_ASSERT (part->disk_specific != NULL);
	dasd_data = part->disk_specific;

	const struct flag_id_mapping_t* p = dasd_find_flag_id_mapping (flag);
	if (p)
		return dasd_data->system == p->type_id;

	return 0;
}

/*
 * The DASD-LDL does not support flags now.
 * So just return 0.
*/
static int
dasd_partition_is_flag_available (const PedPartition* part,
                                  PedPartitionFlag flag)
{
	DasdDiskSpecific* disk_specific;
	PED_ASSERT (part != NULL);
	PED_ASSERT (part->disk != NULL);
	PED_ASSERT (part->disk->disk_specific != NULL);

	disk_specific = part->disk->disk_specific;

	if (disk_specific->format_type == 1)
		return 0;

	if (dasd_find_flag_id_mapping (flag))
		return 1;

	return 0;
}


static int
dasd_get_max_primary_partition_count (const PedDisk* disk)
{
	DasdDiskSpecific* disk_specific;

	disk_specific = disk->disk_specific;
	/* If formated in LDL, maximum partition number is 1 */
	if (disk_specific->format_type == 1)
		return 1;

	return USABLE_PARTITIONS;
}

static bool
dasd_get_max_supported_partition_count (const PedDisk* disk, int *max_n)
{
	*max_n = dasd_get_max_primary_partition_count(disk);
	return true;
}

static PedAlignment*
dasd_get_partition_alignment(const PedDisk *disk)
{
        LinuxSpecific *arch_specific = LINUX_SPECIFIC(disk->dev);
        PedSector sector_size =
                arch_specific->real_sector_size / disk->dev->sector_size;

        return ped_alignment_new(0, disk->dev->hw_geom.sectors * sector_size);
}

static PedConstraint*
_primary_constraint (PedDisk* disk)
{
	PedAlignment start_align;
	PedAlignment end_align;
	PedGeometry	max_geom;
	PedSector sector_size;
	LinuxSpecific* arch_specific;
	DasdDiskSpecific* disk_specific;
	PedSector start;

	PDEBUG;

	arch_specific = LINUX_SPECIFIC (disk->dev);
	disk_specific = disk->disk_specific;
	sector_size = arch_specific->real_sector_size / disk->dev->sector_size;

	if (!ped_alignment_init (&start_align, 0,
							 disk->dev->hw_geom.sectors * sector_size))
		return NULL;
	if (!ped_alignment_init (&end_align, -1,
						     disk->dev->hw_geom.sectors * sector_size))
		return NULL;

	start = (FIRST_USABLE_TRK * (long long) disk->dev->hw_geom.sectors
			    * (long long) arch_specific->real_sector_size
			    / (long long) disk->dev->sector_size);

	if (!ped_geometry_init (&max_geom, disk->dev, start, disk->dev->length))
		return NULL;

	return ped_constraint_new(&start_align, &end_align, &max_geom,
							  &max_geom, 1, disk->dev->length);
}

static int
dasd_partition_align (PedPartition* part, const PedConstraint* constraint)
{
	DasdDiskSpecific* disk_specific;

	PED_ASSERT (part != NULL);

	disk_specific = part->disk->disk_specific;
	/* If formated in LDL, ignore metadata partition */
	if (disk_specific->format_type == 1)
		return 1;

	if (_ped_partition_attempt_align(part, constraint,
								     _primary_constraint(part->disk)))
		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
dasd_partition_enumerate (PedPartition* part)
{
	int i;
	PedPartition* p;

	/* never change the partition numbers */
	if (part->num != -1)
		return 1;

	for (i = 1; i <= USABLE_PARTITIONS; i++) {
		p = ped_disk_get_partition (part->disk, i);
		if (!p) {
			part->num = i;
			return 1;
		}
	}

	/* failed to allocate a number */
	ped_exception_throw(PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
						_("Unable to allocate a dasd disklabel slot"));
	return 0;
}

static int
dasd_partition_set_system (PedPartition* part,
                           const PedFileSystemType* fs_type)
{
	DasdPartitionData* dasd_data = part->disk_specific;
	PedSector cyl_size;

	cyl_size=part->disk->dev->hw_geom.sectors * part->disk->dev->hw_geom.heads;
	PDEBUG;

	part->fs_type = fs_type;

	if (!fs_type) {
		dasd_data->system = PARTITION_LINUX;
        PDEBUG;
	} else if (is_linux_swap (fs_type->name)) {
		dasd_data->system = PARTITION_LINUX_SWAP;
        PDEBUG;
	} else {
		dasd_data->system = PARTITION_LINUX;
        PDEBUG;
	}

	return 1;
}

static int
dasd_alloc_metadata (PedDisk* disk)
{
	PedPartition* new_part;
	PedConstraint* constraint_any = NULL;
	PedSector vtoc_end;
	LinuxSpecific* arch_specific;
	DasdDiskSpecific* disk_specific;
	PedPartition* part = NULL; /* initialize solely to placate gcc */
	PedPartition* new_part2;
	PedSector trailing_meta_start, trailing_meta_end;

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

	arch_specific = LINUX_SPECIFIC (disk->dev);
	disk_specific = disk->disk_specific;

	constraint_any = ped_constraint_any (disk->dev);

	/* For LDL or CMS, the leading metadata ends at the sector before
	   the start of the first partition */
	if (disk_specific->format_type == 1) {
	        part = ped_disk_get_partition(disk, 1);
		if (part)
			vtoc_end = part->geom.start - 1;
		else
			vtoc_end = (PedSector) arch_specific->real_sector_size /
				   (PedSector) disk->dev->sector_size *
				   (PedSector) disk_specific->label_block;
	}
	else {
                if (disk->dev->type == PED_DEVICE_FILE)
                        arch_specific->real_sector_size = disk->dev->sector_size;
        /* Mark the start of the disk as metadata. */
		vtoc_end = (FIRST_USABLE_TRK * (long long) disk->dev->hw_geom.sectors
				   * (long long) arch_specific->real_sector_size
				   / (long long) disk->dev->sector_size) - 1;
        }

	new_part = ped_partition_new (disk,PED_PARTITION_METADATA,NULL,0,vtoc_end);
	if (!new_part)
		goto error;

	if (!ped_disk_add_partition (disk, new_part, constraint_any)) {
		ped_partition_destroy (new_part);
		goto error;
	}

	if (disk_specific->format_type == 1 && part) {
	   /*
	      For LDL or CMS there may be trailing metadata as well.
	      For example: the last block of a CMS reserved file,
	      the "recomp" area of a CMS minidisk that has been
	      formatted and then formatted again with the RECOMP
	      option specifying fewer than the maximum number of
	      cylinders, a disk that was formatted at one size,
	      backed up, then restored to a larger size disk, etc.
	   */
	   trailing_meta_start = part->geom.end + 1;
	   trailing_meta_end = (long long) disk->dev->length - 1;
	   if (trailing_meta_end >= trailing_meta_start) {
		new_part2 = ped_partition_new (disk,PED_PARTITION_METADATA,
		   NULL, trailing_meta_start, trailing_meta_end);
		if (!new_part2) {
		   ped_partition_destroy (new_part);
		   goto error;
		}
		if (!ped_disk_add_partition (disk, new_part2,
		   constraint_any)) {
		   ped_partition_destroy (new_part2);
		   ped_partition_destroy (new_part);
		   goto error;
		}
	   }
	}

	ped_constraint_destroy (constraint_any);
	return 1;

error:
	ped_constraint_destroy (constraint_any);
	return 0;
}
