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

    libparted - a library for manipulating disk partitions
    atari.c - libparted module to manipulate Atari partition tables.
    Copyright (C) 2000-2001, 2004, 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:  Guillaume Knispel <k_guillaume@libertysurf.fr>
                  John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
*/

/*
	Documentation :
		README file of atari-fdisk
		atari-fdisk source code
		Linux atari partitions parser source code
			( fs/partitions/atari.[ch] )
*/

#include <config.h>

#include <string.h>
#include <parted/parted.h>
#include <parted/debug.h>
#include <parted/endian.h>
#include <string.h>
#include <locale.h>
#include <stdint.h>
#include <ctype.h>
#include <stddef.h>

#include "pt-tools.h"

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


/********************** Atari data and structure stuff **********************/

#define BOOTABLE_CKSUM		0x1234
#define NONBOOT_CKSUM		0x4321

#define GEM_MAX			((32*1024*1024)/PED_SECTOR_SIZE_DEFAULT)

#define PART_FLAG_USED		0x01
#define PART_FLAG_BOOT_GEM	0x80	/* GEMDOS	   */
#define PART_FLAG_BOOT_ASV	0x40	/* Atari System V  */
#define PART_FLAG_BOOT_BSD	0x20	/* Net(?)BSD	   */
#define PART_FLAG_BOOT_LNX	0x10	/* Linux	   */
#define PART_FLAG_BOOT_UNK	0x08	/* unknown / other */

#define N_AHDI			4
#define N_ICD			8

#define MAXIMUM_PARTS		64

/* what we put instead of id, start and size in empty */
/* partition tables, to be able to detect it */
#define SIGNATURE_EMPTY_TABLE	"PARTEDATARI"
#define SIGNATURE_EMPTY_SIZE	11

/* to be compared to the last two bytes of 1st sector (Big Endian) */
static const uint16_t atr_forbidden_sign[] = {
	0x55AA,
	0
};

static const char *atr_known_icd_pid[] = {
	"BGM", "GEM", "LNX", "SWP", "RAW", NULL
};

/* static const char *atr_known_pid[] = { */
/* 	"BGM", "GEM", "LNX", "MAC", "MIX", "MNX", "RAW", "SWP", "UNX", */
/* 	"F32", "SV4", NULL */
/* }; */

struct _AtariPartID2BootFlag {
	const char	pid[4];
	uint8_t		flag;
};
typedef struct _AtariPartID2BootFlag AtariPartID2BootFlag;

static AtariPartID2BootFlag atr_pid2bf[] = {
	{ "GEM", PART_FLAG_BOOT_GEM },
	{ "BGM", PART_FLAG_BOOT_GEM },
	{ "UNX", PART_FLAG_BOOT_ASV },
	{ "LNX", PART_FLAG_BOOT_LNX },
	{ "",    PART_FLAG_BOOT_UNK },
};

struct _AtariFS2PartId {
	const char*	fs;
	const char	pid[4];
	PedSector	max_sectors;
};
typedef struct _AtariFS2PartId AtariFS2PartId;

static AtariFS2PartId atr_fs2pid[] = {
/* Other ID are available : MIX MNX <= minix
				UNX <= Atari SysV Unix
				SV4 <= Univ System 4   */
	{ "ext2",	"LNX", INT32_MAX },
	{ "ext3",	"LNX", INT32_MAX },
	{ "fat16",	"GEM",   GEM_MAX },	/* small partitions */
	{ "fat16",	"BGM", INT32_MAX },	/* big partitions */
	{ "fat32",	"F32", INT32_MAX },
	{ "hfs",	"MAC", INT32_MAX },
	{ "hfs+",	"MAC", INT32_MAX },
	{ "hfsx",	"MAC", INT32_MAX },
	{ "jfs",	"LNX", INT32_MAX },
	{ "linux-swap", "SWP", INT32_MAX },
	{ "reiserfs",	"LNX", INT32_MAX },
	{ "hp-ufs",	"LNX", INT32_MAX },
	{ "sun-ufs",	"LNX", INT32_MAX },
	{ "xfs",	"LNX", INT32_MAX },
	{ "ntfs",	"RAW", INT32_MAX },
	{ "",		"RAW", INT32_MAX },	/* default entry */
	{ NULL,		 ""  ,         0 }	/* end of list */
};

struct __attribute__ ((packed)) _AtariRawPartition {
	uint8_t		flag;	/* bit 0: active; bit 7: bootable */
	union {
		uint8_t empty[11];         /* Empty table */
		struct __attribute__ ((packed)) {
			uint8_t		id[3];	/* "GEM", "BGM", "XGM", ... */
			uint32_t	start;	/* start of partition */
			uint32_t	size;	/* length of partition */
		};
	};
};
typedef struct _AtariRawPartition AtariRawPartition;

struct __attribute__ ((packed,aligned(2))) _AtariRawTable {
	uint8_t		  boot_code[0x156]; /* room for boot code */
	AtariRawPartition icd_part[N_ICD];  /* info for ICD-partitions 5..12 */
	uint8_t		  unused[0xc];
	uint32_t	  hd_size;	/* size of disk in blocks */
	AtariRawPartition part[N_AHDI];	/* the four primary partitions */
	uint32_t	  bsl_start;	/* start of bad sector list */
	uint32_t	  bsl_count;	/* length of bad sector list */
	uint16_t	  checksum;	/* checksum for bootable disks */
};
typedef struct _AtariRawTable AtariRawTable;

typedef enum {
	FMT_AHDI = 0,	/* AHDI v1 compatible, no ICD and no XGM */
	FMT_XGM  = 1,	/* AHDI v3 with XGM / this disable ICD */
	FMT_ICD  = 2	/* ICD detected / requested because more than 4 prim */
			/* no XGM allowed */
} AtrFmt;

struct _AtariDisk {
	AtrFmt	format;
	int	has_been_read;	/* actually means has been read or written... */
	uint32_t bsl_start;	/* first sector of the Bad Sectors List */
	uint32_t bsl_count;	/* number of sectors of the BSL */
	uint8_t	HDX_comp;	/* if set to one, atari_write will initialize */
				/* the bsl area */
};
typedef struct _AtariDisk AtariDisk;

struct _AtariPart {
	char	part_id[4];	/* ASCIIZ */
	char	icd_id[4];	/* Linux only parse a limited set of ID */
				/* in ICD (why???), so everything else  */
				/* is translated to RAW.		*/
	uint8_t	flag;		/* without bit 0 (entry used) */
};
typedef struct _AtariPart AtariPart;

/* set by initialisation code to C locale */
static locale_t atr_c_locale;

static PedDiskType atari_disk_type;



/******************************** Atari Code ********************************/

#define ATARI_DISK(disk)	((AtariDisk*)((disk)->disk_specific))
#define ATARI_PART(part)	((AtariPart*)((part)->disk_specific))

#define atr_pid_eq(a,b)		(!memcmp( (a), (b), 3 ))

#define atr_pid_assign(a, b)	(memcpy ( (a), (b), 3 ))

#define atr_part_used(part)	(((part)->flag) & PART_FLAG_USED)

static int
atr_start_size_correct (uint32_t start, uint32_t size, uint32_t hd_size)
{
	uint32_t end = start + size;

	return  end >= start
		&& 0 < start && start <= hd_size
		&& 0 < size && size <= hd_size
		&& 0 < end && end <= hd_size;
}

static int
atr_part_correct (AtariRawPartition* part, uint32_t hd_size)
{
	uint32_t start, size;

	start = PED_BE32_TO_CPU (part->start);
	size = PED_BE32_TO_CPU (part->size);

	return isalnum_l(part->id[0], atr_c_locale)
		&& isalnum_l(part->id[1], atr_c_locale)
		&& isalnum_l(part->id[2], atr_c_locale)
		&& atr_start_size_correct (start, size, hd_size);
}

static int _GL_ATTRIBUTE_PURE
atr_pid_known (const char* pid, const char** pid_list)
{
	for (; *pid_list; pid_list++) {
		if (atr_pid_eq(pid, *pid_list))
			return 1;
	}

	return 0;
}

/* Recognize Parted signature in an AHDI entry, used to
 * identify empty Atari partition tables */
static int
atr_is_signature_entry (AtariRawPartition* part)
{
	return part->flag == 0
		&& !memcmp (part->empty, SIGNATURE_EMPTY_TABLE,
					 SIGNATURE_EMPTY_SIZE );
}

/* Set Parted signature in an AHDI entry */
static void
atr_put_signature_entry (AtariRawPartition* part)
{
	part->flag = 0;
	memcpy (part->empty, SIGNATURE_EMPTY_TABLE, SIGNATURE_EMPTY_SIZE);
}

#define atr_part_known(part, pid_list) (atr_pid_known ((part)->id, pid_list))

#define atr_part_valid(part, sz) (atr_part_used(part)\
				  && atr_part_correct((part), (sz)))
#define atr_part_trash(part, sz) (atr_part_used(part)\
				  && !atr_part_correct((part), (sz)))

/* Check if this device can be used with an Atari label */
static int
atr_can_use_dev (const PedDevice *dev)
{
	/* i really don't know how atari behave with non 512 bytes */
	/* sectors... */
	if (dev->sector_size != PED_SECTOR_SIZE_DEFAULT) {
		ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_CANCEL,
			_("Can't use Atari partition tables on disks with a "
			  "sector size not equal to %d bytes."),
			(int)PED_SECTOR_SIZE_DEFAULT );
		return 0;
	}

	/* the format isn't well defined enough to support > 0x7FFFFFFF */
	/* sectors */
	if (dev->length > INT32_MAX) {
		ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_CANCEL,
			_("Can't use Atari partition tables on disks with more "
			  "than %d sectors."),
			INT32_MAX );
		return 0;
	}

	return 1;
}

/*
 * The Atari disk label doesn't have any magic id
 * so we must completely parse the layout to be sure
 * we are really dealing with it.
 */
static int
atari_probe (const PedDevice *dev)
{
	AtariRawTable	table;
	uint32_t	rs_hd_size, parts, exts;
	int		valid_count, xgm_part, xgm_num, i;
	int		num_sign, total_count = 0;

	PED_ASSERT (dev != NULL);
	PED_ASSERT (sizeof(table) == 512);

	/* Device Spec ok for Atari label? */
	if (!atr_can_use_dev (dev))
		return 0;

	/* read the root sector */
	if (!ped_device_read (dev, &table, 0, 1))
		return 0;

	/* number of sectors stored in the root sector > device length ? */
	/* => just reject the Atari disk label */
	rs_hd_size = PED_BE32_TO_CPU (table.hd_size);
	if (rs_hd_size > dev->length
	    || rs_hd_size < 2)
		return 0;

	/* check the BSL fields */
	if ((table.bsl_start || table.bsl_count)
	    && !atr_start_size_correct (PED_BE32_TO_CPU (table.bsl_start),
					PED_BE32_TO_CPU (table.bsl_count),
					rs_hd_size ) )
		return 0;

	/* scan the main AHDI fields */
	num_sign = 0; xgm_num = 0;
	valid_count = 0; xgm_part = 0;
	for (i = 0; i < N_AHDI; i++) {
		if (atr_part_valid (&table.part[i], rs_hd_size)) {
			valid_count++;
			total_count++;
			if (atr_pid_eq(table.part[i].id, "XGM")) {
				xgm_part++;
				xgm_num = i;
			}
		} else if (atr_part_trash (&table.part[i], rs_hd_size)) {
			return 0;
		}
		if (atr_is_signature_entry (&table.part[i]))
			num_sign++;
	}

	/* no way to reliably detect empty Atari disk labels if
	 *    they aren't using parted signature in 4 prim fields
	 * && reject multi XGM labels because Parted can't handle
	 *    multiple extended partitions
	 * && reject if xgm partition in slot 0 because not allowed */
	if ((!valid_count && num_sign != N_AHDI)
	    || xgm_part > 1
	    || (xgm_part == 1 && xgm_num == 0) )
		return 0;

	/* check coherency of each logical partitions and ARS */
	if (xgm_part) { /* ! WARNING ! reuses "table" */
		/* we must allow empty ext partition even if they're   */
		/* not valid because parted write the layout to the HD */
		/* at each operation, and we can't create ext and log  */
		/* at the same time */
		int	empty_ars_allowed = 1;

		parts = exts = PED_BE32_TO_CPU (table.part[xgm_num].start);
		while (1) {
			if (!ped_device_read (dev, &table, parts, 1))
				return 0;

			for (i = 0; i < N_AHDI-1; ++i) {
				if (atr_part_used (&table.part[i]))
					break;
			}

			/* we allow the ext part to be empty (see above) */
			if (i == N_AHDI-1 && empty_ars_allowed)
				break;

			/* data partition must be in slot 0, 1 or 2 */
			if (i == N_AHDI-1
			    || !atr_part_correct (&table.part[i], rs_hd_size
								  - parts )
			    || atr_pid_eq (table.part[i].id, "XGM"))
				return 0;

			/* If there is at least one logical partition */
			/* then next ARS should not be empty */
			empty_ars_allowed = 0;

			total_count++;
			if (total_count > MAXIMUM_PARTS) {
				ped_exception_throw (
					PED_EXCEPTION_ERROR,
					PED_EXCEPTION_CANCEL,
					_("Too many Atari partitions detected. "
					" Maybe there is a loop in the XGM "
					"linked list.  Aborting.") );
				return 0;
			}

			/* end of logical partitions? */
			if (!atr_part_used (&table.part[i+1]))
				break;

			/* is this really the descriptor of the next ARS? */
			if (!atr_part_correct (&table.part[i+1], rs_hd_size
								  - exts )
			    || !atr_pid_eq (table.part[i+1].id, "XGM"))
				return 0;

			parts = exts + PED_BE32_TO_CPU (table.part[i+1].start);
		}
	} /* no XGM so try ICD */
	  else if (atr_part_valid (&table.icd_part[0], rs_hd_size)
		   && atr_part_known (&table.icd_part[0], atr_known_icd_pid)) {
		for (i = 1; i < N_ICD; i++) {
			if (atr_part_trash (&table.icd_part[i], rs_hd_size))
				return 0;
		}
	}

	return 1;
}

static void
atr_disk_reset (AtariDisk* atr_disk)
{
	/* Empty partition table => only AHDI needed right now */
	atr_disk->format = FMT_AHDI;
	/* The disk is not in sync with the actual content of the label */
	atr_disk->has_been_read = 0;
	/* Create an empty BSL for HDX compatibility */
	atr_disk->bsl_start = 1;
	atr_disk->bsl_count = 1;
	atr_disk->HDX_comp = 1;
}

/*
 * Must set up the PedDisk and the associated AtariDisk as if
 * the user is doing mklabel, since in this case atari_alloc
 * is called alone whereas when reading an existing partition
 * table atari_read is called after atari_alloc and can overwrite
 * the settings.
 */
static PedDisk*
atari_alloc (const PedDevice* dev)
{
	PedDisk*	disk;
	AtariDisk*	atr_disk;

	PED_ASSERT (dev != NULL);

	if (!atr_can_use_dev (dev)
	    || !(disk = _ped_disk_alloc (dev, &atari_disk_type)))
		return NULL;

	if (!(disk->disk_specific = atr_disk = ped_malloc (sizeof (AtariDisk))))
		goto error_free_disk;

	atr_disk_reset (atr_disk);

	return disk;

error_free_disk:
	free (disk);
	return NULL;
}

static PedDisk*
atari_duplicate (const PedDisk* disk)
{
	PedDisk*	new_disk;
	AtariDisk*	old_atr_dsk;
	AtariDisk*	new_atr_dsk;

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

	old_atr_dsk = ATARI_DISK (disk);
	if (!(new_disk = ped_disk_new_fresh (disk->dev, &atari_disk_type)))
		return NULL;
	new_atr_dsk = ATARI_DISK (new_disk);

	memcpy (new_atr_dsk, old_atr_dsk, sizeof(*old_atr_dsk));

	return new_disk;
}

static void
atari_free (PedDisk* disk)
{
	AtariDisk* atr_disk;
	PED_ASSERT (disk != NULL);
	PED_ASSERT (disk->disk_specific != NULL);
	atr_disk = ATARI_DISK (disk);

	_ped_disk_free (disk);
	free (atr_disk);
}

/* Warning : ID not ASCIIZ but 3 chars long */
static void
atr_part_sysraw (PedPartition* part, const char* id, uint8_t flag)
{
	AtariPart* atr_part = ATARI_PART (part);

	atr_part->flag = flag & ~PART_FLAG_USED;

	atr_pid_assign (atr_part->part_id, id);
	atr_part->part_id[3] = 0;

	if (atr_pid_known (id, atr_known_icd_pid)) {
		atr_pid_assign (atr_part->icd_id, id);
		atr_part->icd_id[3] = 0;
	} else {
		atr_pid_assign (atr_part->icd_id, "RAW");
		atr_part->icd_id[3] = 0;
	}
}

static int
atr_parse_add_rawpart (PedDisk* disk, PedPartitionType type, PedSector st_off,
		       int num, const AtariRawPartition* rawpart )
{
	PedSector	start, end;
	PedPartition* 	part;
	PedConstraint*	const_exact;
	int		added;

	start = st_off + PED_BE32_TO_CPU (rawpart->start);
	end = start + PED_BE32_TO_CPU (rawpart->size) - 1;

	part = ped_partition_new (disk, type, NULL, start, end);
	if (!part)
		return 0;

	/*part->num = num;*/	/* Enumeration will take care of that */
	part->num = -1;		/* Indeed we can't enumerate here
				 * because the enumerate function uses
				 * -1 do detect new partition being
				 * inserted and update the atrdisk->format */
	if (type != PED_PARTITION_EXTENDED)
		part->fs_type = ped_file_system_probe (&part->geom);
	else
		part->fs_type = NULL;
	atr_part_sysraw (part, rawpart->id, rawpart->flag);

	const_exact = ped_constraint_exact (&part->geom);
	added = ped_disk_add_partition (disk, part, const_exact);
	ped_constraint_destroy (const_exact);
	if (!added) {
		ped_partition_destroy (part);
		return 0;
	}

	PED_ASSERT (part->num == num);
	return 1;
}

/*
 * Read the chained list of logical partitions.
 * exts points to the first Auxiliary Root Sector, at the start
 * of the extended partition.
 * In each ARS one partition entry describes to the logical partition
 * (start relative to the ARS position) and the next entry with ID "XGM"
 * points to the next ARS (start relative to exts).
 */
static int
atr_read_logicals (PedDisk* disk, PedSector exts, int* pnum)
{
	AtariRawTable	table;
	PedSector	parts = exts;
	int		i, empty_ars_allowed = 1;

	while (1) {
		if (!ped_device_read (disk->dev, &table, parts, 1))
			return 0;

		for (i = 0; i < N_AHDI-1; ++i)
			if (atr_part_used (&table.part[i]))
				break;

		if (i == N_AHDI-1 && empty_ars_allowed)
			break;

		/* data partition must be in slot 0, 1 or 2 */
		if (i == N_AHDI-1
		    || atr_pid_eq (table.part[i].id, "XGM")) {
			ped_exception_throw (
				PED_EXCEPTION_ERROR,
				PED_EXCEPTION_CANCEL,
				_("No data partition found in the ARS at "
				  "sector %lli."), parts );
			return 0;
		}

		empty_ars_allowed = 0;

		if (!atr_parse_add_rawpart (disk, PED_PARTITION_LOGICAL,
					    parts, *pnum, &table.part[i] ) )
			return 0;

		(*pnum)++;

		/* end of logical partitions? */
		if (!atr_part_used (&table.part[i+1]))
			break;

		if (!atr_pid_eq (table.part[i+1].id, "XGM")) {
			ped_exception_throw (
				PED_EXCEPTION_ERROR,
				PED_EXCEPTION_CANCEL,
				_("The entry of the next logical ARS is not of "
				  "type XGM in ARS at sector %lli."), parts );
			return 0;
		}

		parts = exts + PED_BE32_TO_CPU (table.part[i+1].start);
	}

	return 1;
}

static int
atari_read (PedDisk* disk)
{
	AtariRawTable	table;
	AtariDisk*	atr_disk;
	uint32_t	rs_hd_size;
	int		i, pnum, xgm, pcount;

	PED_ASSERT (disk != NULL);
	PED_ASSERT (disk->dev != NULL);
	PED_ASSERT (disk->disk_specific != NULL);
	atr_disk = ATARI_DISK (disk);

	ped_disk_delete_all (disk);
	atr_disk_reset (atr_disk);

	if (!atari_probe (disk->dev)) {
		if (ped_exception_throw (
			PED_EXCEPTION_ERROR,
			PED_EXCEPTION_IGNORE_CANCEL,
			_("There doesn't seem to be an Atari partition table "
			  "on this disk (%s), or it is corrupted."),
			disk->dev->path )
				!= PED_EXCEPTION_IGNORE)
			return 0;
	}

	if (!ped_device_read (disk->dev, (void*) &table, 0, 1))
		goto error;

	/* We are sure that the layout looks coherent so we
	   don't need to check too much */

	rs_hd_size = PED_BE32_TO_CPU (table.hd_size);
	atr_disk->bsl_start = PED_BE32_TO_CPU (table.bsl_start);
	atr_disk->bsl_count = PED_BE32_TO_CPU (table.bsl_count);
	atr_disk->HDX_comp = 0;

	/* AHDI primary partitions */
	pnum = 1; xgm = 0; pcount = 0;
	for (i = 0; i < N_AHDI; i++) {
		if (!atr_part_used (&table.part[i]))
			continue;

		pcount++;

		if (atr_pid_eq (table.part[i].id, "XGM")) {

			atr_disk->format = FMT_XGM;
			xgm = 1;
			if (!atr_parse_add_rawpart(disk, PED_PARTITION_EXTENDED,
						   0, 0, &table.part[i] )
			    || !atr_read_logicals (
					disk,
					PED_BE32_TO_CPU (table.part[i].start),
					&pnum ) )
				goto error;

		} else {

			if (!atr_parse_add_rawpart (disk, PED_PARTITION_NORMAL,
						    0, pnum, &table.part[i] ) )
				goto error;
			pnum++;
		}
	}

	/* If no XGM partition has been found, the AHDI table is not empty,  */
	/* the first entry is valid and its ID ok for ICD, then we parse the */
	/* ICD table. */
	if (!xgm && pcount != 0
		 && atr_part_valid (&table.icd_part[0], rs_hd_size)
		 && atr_part_known (&table.icd_part[0], atr_known_icd_pid))
	for (i = 0; i < N_ICD; i++) {

		if (!atr_part_known (&table.icd_part[i], atr_known_icd_pid)
		    || !atr_part_used (&table.icd_part[i]))
			continue;
		atr_disk->format = FMT_ICD;

		if (!atr_parse_add_rawpart (disk, PED_PARTITION_NORMAL,
					    0, pnum, &table.icd_part[i] ) )
			goto error;
		pnum++;
	}

	atr_disk->has_been_read = 1;
	return 1;

error:
	ped_disk_delete_all (disk);
	atr_disk_reset (atr_disk);
	return 0;
}

/* Returns the number of the first logical partition or -1 if not found */
static int
atr_find_first_log (const PedDisk* disk)
{
	PedPartition*	part;
	int		first_log, last;

	last = ped_disk_get_last_partition_num (disk);

	for (first_log = 1; first_log <= last; first_log++) {
		if ((part = ped_disk_get_partition (disk, first_log))
		     && (part->type & PED_PARTITION_LOGICAL))
			break;
	}

	return first_log > last ? -1 : first_log;
}

#ifndef DISCOVER_ONLY
static int
atari_clobber (PedDevice* dev)
{
	AtariRawTable table;

	PED_ASSERT (dev != NULL);
	PED_ASSERT (atari_probe (dev));

	if (!ped_device_read (dev, &table, 0, 1))
		return 0;

	/* clear anything but the boot code and the optional ICD table */
	memset (table.boot_code + offsetof (AtariRawTable, hd_size),
		0,
		PED_SECTOR_SIZE_DEFAULT - offsetof (AtariRawTable, hd_size));

	return ped_device_write (dev, &table, 0, 1);
}

/* Computes the checksum of the root sector */
static uint16_t
atr_calc_rs_sum (const AtariRawTable* table)
{
	const uint16_t* word = (uint16_t*)(table);
	const uint16_t* end  = (uint16_t*)(table + 1);
	uint16_t sum;

	for (sum = 0; word < end; word++)
		sum += PED_BE16_TO_CPU(*word);

	return sum;
}

/* Returns 1 if the root sector is bootable, else returns 0 */
static int
atr_is_boot_table (const AtariRawTable* table)
{
	return atr_calc_rs_sum (table) == BOOTABLE_CKSUM;
}

/*
 * Returns 1 if sign belongs to a set of `forbidden' signatures.
 * (e.g.: 55AA which is the MSDOS siganture...)
 * Only used for non bootable root sector since the signature of
 * a bootable one is unique.
 */
static int _GL_ATTRIBUTE_PURE
atr_sign_is_forbidden (uint16_t sign)
{
	const uint16_t* forbidden;

	for (forbidden = atr_forbidden_sign; *forbidden; forbidden++) {
		if (sign == *forbidden)
			return 1;
	}

	return 0;
}

/* Updates table->checksum so the RS will be considered bootable (or not) */
static void
atr_table_set_boot (AtariRawTable* table, int boot)
{
	uint16_t boot_cksum, noboot_cksum;
	uint16_t sum;

	table->checksum = 0;
	sum = atr_calc_rs_sum (table);
	boot_cksum = BOOTABLE_CKSUM - sum;

	if (boot) {
		table->checksum = PED_CPU_TO_BE16 (boot_cksum);
		return;
	}

	noboot_cksum = NONBOOT_CKSUM - sum;

	while (atr_sign_is_forbidden (noboot_cksum)
	       || noboot_cksum == boot_cksum)
		noboot_cksum++;

	table->checksum = PED_CPU_TO_BE16 (noboot_cksum);
}

/* Fill an used partition entry */
static void
atr_fill_raw_entry (AtariRawPartition* rawpart, uint8_t flag, const char* id,
		    uint32_t start, uint32_t size )
{
	rawpart->flag = PART_FLAG_USED | flag;
	atr_pid_assign (rawpart->id, id);
	rawpart->start = PED_CPU_TO_BE32 (start);
	rawpart->size = PED_CPU_TO_BE32 (size);
}

static int
atr_write_logicals (const PedDisk* disk)
{
	AtariRawTable	table;
	PedPartition*	log_curr;
	PedPartition*	log_next;
	PedPartition*	ext;
	PedPartition*	part;
	PedSector	exts;
	PedSector	parts;
	AtariPart*	atr_part;
	int		first_log, pnum, i;

	PED_ASSERT (disk != NULL);

	ext = ped_disk_extended_partition (disk);
	exts = parts = ext->geom.start;

	pnum = first_log = atr_find_first_log (disk);

	while (1) {
		if (pnum != -1) {
			log_curr = ped_disk_get_partition (disk, pnum);
			log_next = ped_disk_get_partition (disk, pnum + 1);
		} else {
			log_curr = log_next = NULL;
		}

		if (log_curr && !(log_curr->type & PED_PARTITION_LOGICAL))
			log_curr = NULL;
		if (log_next && !(log_next->type & PED_PARTITION_LOGICAL))
			log_next = NULL;

		PED_ASSERT (pnum == first_log || log_curr);

		part = ped_disk_get_partition_by_sector (disk, parts);
		if (part && ped_partition_is_active (part)) {
			if (log_curr)
				ped_exception_throw (
					PED_EXCEPTION_ERROR,
					PED_EXCEPTION_CANCEL,
					_("No room at sector %lli to store ARS "
					  "of logical partition %d."),
					parts, pnum );
			else
				ped_exception_throw (
					PED_EXCEPTION_ERROR,
					PED_EXCEPTION_CANCEL,
				      _("No room at sector %lli to store ARS."),
					parts );
			return 0;
		}

		if (!ped_device_read (disk->dev, &table, parts, 1))
			return 0;

		if (!log_curr) {
			PED_ASSERT (!log_next);

			for (i = 0; i < N_AHDI; i++)
				table.part[i].flag &= ~PART_FLAG_USED;
		} else {
			atr_part = ATARI_PART (log_curr);
			atr_fill_raw_entry (&table.part[0], atr_part->flag,
					    atr_part->part_id,
					    log_curr->geom.start - parts,
					    log_curr->geom.length );

			for (i = 1; i < N_AHDI; i++)
				table.part[i].flag &= ~PART_FLAG_USED;

			if (log_next) {
				atr_fill_raw_entry (&table.part[1], 0, "XGM",
					log_next->geom.start - 1 - exts,
					log_next->geom.length + 1 );
			}
		}

		/* TODO: check if we can set that bootable, and when */
		atr_table_set_boot (&table, 0);

		if (!ped_device_write (disk->dev, &table, parts, 1))
			return 0;

		if (!log_next)
			break;

		parts = log_next->geom.start - 1;
		pnum++;
	}

	return 1;
}

static int _GL_ATTRIBUTE_PURE
_disk_logical_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;
}

/* Load the HD size from the table and ask to fix it if != device size. */
static int
atr_load_fix_hdsize (const PedDisk* disk, uint32_t* rs_hd_size, AtariRawTable* table)
{
	AtariDisk*	atr_disk = ATARI_DISK (disk);
	int		result = PED_EXCEPTION_UNHANDLED;

	*rs_hd_size = PED_BE32_TO_CPU (table->hd_size);
	if (*rs_hd_size != disk->dev->length) {
		if (atr_disk->has_been_read) {
			result = ped_exception_throw (
				PED_EXCEPTION_WARNING,
				PED_EXCEPTION_FIX | PED_EXCEPTION_IGNORE_CANCEL,
				_("The sector count that is stored in the "
				  "partition table does not correspond "
				  "to the size of your device.  Do you "
				  "want to fix the partition table?") );
			if (result == PED_EXCEPTION_CANCEL)
				return 0;
		}

		if (result == PED_EXCEPTION_UNHANDLED)
			result = PED_EXCEPTION_FIX;

		if (result == PED_EXCEPTION_FIX) {
			*rs_hd_size = disk->dev->length;
			table->hd_size = PED_CPU_TO_BE32(*rs_hd_size);
		}
	}
	return 1;
}

/* Try to init the HDX compatibility Bad Sectors List. */
static int
atr_empty_init_bsl (const PedDisk* disk)
{
	uint8_t		zeros[PED_SECTOR_SIZE_DEFAULT];
	PedSector	sec;
	PedPartition*	part;
	AtariDisk*	atr_disk = ATARI_DISK (disk);

	memset (zeros, 0, PED_SECTOR_SIZE_DEFAULT);
	for (sec = atr_disk->bsl_start;
	     sec < atr_disk->bsl_start + atr_disk->bsl_count;
	     sec++ ) {
		if (sec == atr_disk->bsl_start)
			zeros[3] = 0xA5;
		else
			zeros[3] = 0;
		part = ped_disk_get_partition_by_sector (disk, sec);
		if (part && ped_partition_is_active (part)) {
			ped_exception_throw (
				PED_EXCEPTION_ERROR,
				PED_EXCEPTION_CANCEL,
				_("No room at sector %lli to store BSL."),
				sec );
			return 0;
		}
		ped_device_write (disk->dev, zeros, sec, 1);
	}
	atr_disk->HDX_comp = 0;
	return 1;
}

static int
atari_write (const PedDisk* disk)
{
	AtariRawTable	table;
	AtariDisk*	atr_disk;
	AtariPart*	atr_part;
	PedPartition*	log;
	PedPartition*	ext_part;
	PedPartition*	part = NULL;
	uint32_t	rs_hd_size;
	int		i, xgm_begin, pnum, append_ext;
	int		put_sign, boot, prim_count, last_num;
	PED_ASSERT (disk != NULL);
	PED_ASSERT (disk->dev != NULL);
	atr_disk = ATARI_DISK (disk);
	PED_ASSERT (atr_disk != NULL);

	prim_count = ped_disk_get_primary_partition_count (disk);
	last_num = ped_disk_get_last_partition_num (disk);
	ext_part = ped_disk_extended_partition (disk);

	/* WARNING: similar/related code in atari_enumerate */
	xgm_begin = ((log = ped_disk_get_partition (disk, 1))
		      && (log->type & PED_PARTITION_LOGICAL));
	PED_ASSERT (atr_disk->format != FMT_ICD || ext_part == NULL);
	PED_ASSERT (atr_disk->format != FMT_XGM || prim_count + xgm_begin <= N_AHDI);
	PED_ASSERT (atr_disk->format != FMT_AHDI || (ext_part == NULL && prim_count + xgm_begin <= N_AHDI));

	/* Device Spec ok for Atari label? */
	if (!atr_can_use_dev (disk->dev))
		goto error;

	if (!ped_device_read (disk->dev, (void*) &table, 0, 1))
		goto error;

	boot = atr_is_boot_table (&table);

	table.bsl_start = PED_CPU_TO_BE32 (atr_disk->bsl_start);
	table.bsl_count = PED_CPU_TO_BE32 (atr_disk->bsl_count);

	/* Before anything else check the sector count and */
	/* fix it if necessary */
	if (!atr_load_fix_hdsize (disk, &rs_hd_size, &table))
		goto error;

	append_ext =    (ext_part != NULL)
		     && (_disk_logical_partition_count (disk) == 0);

	/* Fill the AHDI table */
	put_sign = (prim_count == 0);
	pnum = 1;
	for (i = 0; i < N_AHDI; i++) {
		if (pnum > last_num)
			part = NULL;
		else while (pnum <= last_num
			    && !(part = ped_disk_get_partition (disk, pnum)))
			pnum++;

		if (put_sign) {
			atr_put_signature_entry (&table.part[i]);
			continue;
		}

		if (!part && i != 0 && append_ext) {
			part = ext_part;
			append_ext = 0;
		}

		if (!part || (i == 0 && xgm_begin)) {
			table.part[i].flag &= ~PART_FLAG_USED;
			continue;
		}

		if (part->type & PED_PARTITION_LOGICAL)
			part = ext_part;

		PED_ASSERT (part != NULL);

		atr_part = ATARI_PART (part);
		atr_fill_raw_entry (&table.part[i], atr_part->flag,
				    atr_part->part_id, part->geom.start,
				    part->geom.length );

		if (part->type & PED_PARTITION_EXTENDED) {
			while (pnum <= last_num) {
				part = ped_disk_get_partition (disk, pnum);
				if (part &&
				    !(part->type & PED_PARTITION_LOGICAL))
					break;
				pnum++;
			}
		} else
			pnum++;
	}

	if ((ext_part != NULL || atr_disk->format == FMT_AHDI)
	    && pnum <= last_num) {
		ped_exception_throw (PED_EXCEPTION_BUG, PED_EXCEPTION_CANCEL,
			_("There were remaining partitions after filling "
			  "the main AHDI table.") );
		goto error;
	}

	/* Leave XGM or ICD mode if uneeded */
	if (pnum > last_num
	    && (atr_disk->format == FMT_ICD || ext_part == NULL))
		atr_disk->format = FMT_AHDI;

	/* If AHDI mode, check that no ICD will be detected */
	/* and propose to fix */
	if (atr_disk->format == FMT_AHDI
	    && atr_part_valid (&table.icd_part[0], rs_hd_size)
	    && atr_part_known (&table.icd_part[0], atr_known_icd_pid)) {
		int result = PED_EXCEPTION_UNHANDLED;
		result = ped_exception_throw (
			PED_EXCEPTION_WARNING,
			PED_EXCEPTION_YES_NO_CANCEL,
			_("The main AHDI table has been filled with all "
			  "partitions but the ICD table is not empty "
			  "so more partitions of unknown size and position "
			  "will be detected by ICD compatible software.  Do "
			  "you want to invalidate the ICD table?") );
		if (result == PED_EXCEPTION_YES
		    || result == PED_EXCEPTION_UNHANDLED)
			table.icd_part[0].flag &= ~PART_FLAG_USED;
		else if (result == PED_EXCEPTION_CANCEL)
			goto error;
	}

	if (put_sign)
		goto write_to_dev;

	/* Fill the ICD table */
	if (atr_disk->format == FMT_ICD)
	for (i = 0; i < N_ICD; i++) {
		if (pnum > last_num)
			part = NULL;
		else while (pnum <= last_num
			    && !(part = ped_disk_get_partition (disk, pnum)))
			pnum++;

		if (!part) {
			table.icd_part[i].flag &= ~PART_FLAG_USED;
			continue;
		}

		if (part->type & PED_PARTITION_EXTENDED
		    || part->type & PED_PARTITION_LOGICAL) {
			ped_exception_throw (
				PED_EXCEPTION_BUG,
				PED_EXCEPTION_CANCEL,
				_("ICD entries can't contain extended or "
				  "logical partitions.") );
			goto error;
		}

		atr_part = ATARI_PART (part);
		atr_fill_raw_entry (&table.icd_part[i], atr_part->flag,
				    atr_part->icd_id, part->geom.start,
				    part->geom.length );

		pnum++;
	}

	/* Write the chained list of logical partitions */
	if (atr_disk->format == FMT_XGM) {
		if (!atr_write_logicals (disk))
			goto error;
	}

write_to_dev:
	if (pnum <= last_num) {
		ped_exception_throw (PED_EXCEPTION_BUG, PED_EXCEPTION_CANCEL,
			_("There were remaining partitions after filling "
			  "the tables.") );
		goto error;
	}

	/* Do we need to do that in case of failure too??? */
	atr_table_set_boot (&table, boot);

	/* Commit the root sector... */
	if (!ped_device_write (disk->dev, (void*) &table, 0, 1)
	    || !ped_device_sync (disk->dev))
		goto error;

	/* Try to init the HDX compatibility Bad Sectors List if needed. */
	if (atr_disk->HDX_comp && !atr_empty_init_bsl (disk))
		goto error;

	atr_disk->has_been_read = 1;
	return ped_device_sync (disk->dev);

error:
	atr_disk->has_been_read = 0;
	return 0;
}
#endif

/* If extended partition in ICD mode, generate an error and returns 1 */
/* else returns 0 */
static int
atr_xgm_in_icd (const PedDisk* disk, PedPartitionType part_type)
{
	AtariDisk* atrdisk;

	PED_ASSERT (disk != NULL);

	if (part_type & PED_PARTITION_EXTENDED) {
		atrdisk = ATARI_DISK (disk);
		if (atrdisk->format == FMT_ICD) {
			ped_exception_throw (
			      PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
			      _("You can't use an extended XGM partition in "
				"ICD mode (more than %d primary partitions, if "
				"XGM is the first one it counts for two)."),
				N_AHDI );
			return 1;
		}
	}

	return 0;
}

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

	if (atr_xgm_in_icd(disk, part_type))
		return 0;

	part = _ped_partition_alloc (disk, part_type, fs_type, start, end);
	if (!part)
		goto error;
	if (ped_partition_is_active (part)) {
		part->disk_specific = atrpart = ped_malloc (sizeof (AtariPart));
		if (!atrpart)
			goto error_free_part;
		memset (atrpart, 0, sizeof (AtariPart));
	} else {
		part->disk_specific = NULL;
	}
	return part;

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

static PedPartition*
atari_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;
	if (ped_partition_is_active (part))
		memcpy (new_part->disk_specific, part->disk_specific,
			sizeof (AtariPart));

	return new_part;
}

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

	if (ped_partition_is_active (part)) {
		PED_ASSERT (part->disk_specific != NULL);
		free (part->disk_specific);
	}
	_ped_partition_free (part);
}

/* Note: fs_type is NULL for extended partitions */
static int
atari_partition_set_system (PedPartition* part,
			    const PedFileSystemType* fs_type)
{
	AtariPart*	atrpart;
	AtariFS2PartId* fs2id;
	PED_ASSERT (part != NULL);
	atrpart = ATARI_PART (part);
	PED_ASSERT (atrpart != NULL);

	part->fs_type = fs_type;

	if (atr_xgm_in_icd(part->disk, part->type))
		return 0;

	if (part->type & PED_PARTITION_EXTENDED) {
		strcpy (atrpart->part_id, "XGM");
		strcpy (atrpart->icd_id,  "XGM");
		return 1;
	}

	if (!fs_type) {
		strcpy (atrpart->part_id, "RAW");
		strcpy (atrpart->icd_id,  "RAW");
		return 1;
	}

	for (fs2id = atr_fs2pid; fs2id->fs; fs2id++) {
		if (!*fs2id->fs    /* default entry */
		    || ((!strcmp (fs_type->name, fs2id->fs)
		        && part->geom.length < fs2id->max_sectors))) {

			strcpy (atrpart->part_id, fs2id->pid);
			if (atr_pid_known (fs2id->pid, atr_known_icd_pid))
				strcpy (atrpart->icd_id, fs2id->pid);
			else
				strcpy (atrpart->icd_id, "RAW");

			break;
		}
	}
	PED_ASSERT (fs2id->fs != NULL);

	return 1;
}

static int
atari_partition_set_flag (PedPartition* part, PedPartitionFlag flag, int state)
{
	AtariPart* atr_part;
	AtariPartID2BootFlag* bf;

	PED_ASSERT (part != NULL);
	atr_part = ATARI_PART (part);
	PED_ASSERT (atr_part != NULL);

	if (flag != PED_PARTITION_BOOT)
		return 0;

	if (state == 0) {
		atr_part->flag = 0;
	} else {
		for (bf = atr_pid2bf; *bf->pid; bf++) {
			if (atr_pid_eq (bf->pid, atr_part->part_id))
				break;
		}
		atr_part->flag = bf->flag;
	}

	return 1;
}

static int _GL_ATTRIBUTE_PURE
atari_partition_get_flag (const PedPartition* part, PedPartitionFlag flag)
{
	AtariPart* atr_part;

	PED_ASSERT (part != NULL);
	atr_part = ATARI_PART (part);
	PED_ASSERT (atr_part != NULL);

	if (flag != PED_PARTITION_BOOT)
		return 0;

	return (atr_part->flag != 0);
}

static int
atari_partition_is_flag_available (const PedPartition* part,
				   PedPartitionFlag flag)
{
	if (flag == PED_PARTITION_BOOT)
		return 1;

	return 0;
}

/* Adapted from disk_dos */
static PedConstraint*
atr_log_constraint (const PedPartition* part)
{
	const PedGeometry*	geom = &part->geom;
	PedGeometry	safe_space;
	PedSector	min_start;
	PedSector	max_end;
	PedDisk*	disk;
	PedDevice*	dev;
	PedPartition*	ext_part;
	PedPartition*	walk;
	int		first_log, not_first;

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

	dev = (disk = part->disk) -> dev;

	first_log = atr_find_first_log (disk);
	if (first_log == -1)
		first_log = part->num;

	not_first = (part->num != first_log);

	walk = ext_part->part_list;

	min_start = ext_part->geom.start + 1 + not_first;
	max_end = ext_part->geom.end;

	while (walk != NULL
		&& (   walk->geom.start - (walk->num != first_log)
						< geom->start - not_first
		    || walk->geom.start - (walk->num != first_log)
						< min_start ) ) {
		if (walk != part && ped_partition_is_active (walk))
			min_start = walk->geom.end + 1 + not_first;
		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 != first_log);

	if (min_start >= max_end)
		return NULL;

	ped_geometry_init (&safe_space, dev, min_start,
			   max_end - min_start + 1);
	return ped_constraint_new_from_max (&safe_space);
}

/* Adapted from disk_dos */
static PedGeometry*
art_min_extended_geom (const PedPartition* ext_part)
{
	PedDisk*	disk = ext_part->disk;
	PedPartition*	walk;
	PedGeometry*	min_geom;
	int		first_log;

	first_log = atr_find_first_log (disk);
	if (first_log == -1)
		return NULL;

	walk = ped_disk_get_partition (disk, first_log);
	PED_ASSERT (walk->type & PED_PARTITION_LOGICAL);
	min_geom = ped_geometry_duplicate (&walk->geom);
	if (!min_geom)
		return NULL;
	ped_geometry_set_start (min_geom, walk->geom.start - 1);

	for (walk = ext_part->part_list; walk; walk = walk->next) {
		if (!ped_partition_is_active (walk) || walk->num == first_log)
			continue;
		if (walk->geom.start < min_geom->start)
			ped_geometry_set_start (min_geom, walk->geom.start - 2);
		if (walk->geom.end > min_geom->end)
			ped_geometry_set_end (min_geom, walk->geom.end);
	}

	return min_geom;
}

/* Adapted from disk_dos */
static PedConstraint*
atr_ext_constraint (const PedPartition* part)
{
	PedGeometry	start_range;
	PedGeometry	end_range;
	PedConstraint*	constraint;
	PedDevice*	dev;
	PedDisk*	disk;
	PedGeometry*	min;

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

	dev = (disk = part->disk) -> dev;
	min = art_min_extended_geom (part);

	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 PedConstraint*
atr_prim_constraint (const PedPartition* part)
{
	PedDevice*	dev;
	PedGeometry	max;

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

	dev = part->disk->dev;

	ped_geometry_init (&max, dev, 1, dev->length - 1);
	return ped_constraint_new_from_max (&max);
}

/* inspiration from disk_dos */
static PedGeometry*
_best_solution (PedGeometry* a, PedGeometry* b)
{
	if (!a)
		return b;
	if (!b)
		return a;

	if (a->length < b->length)
		goto choose_b;

	ped_geometry_destroy (b);
	return a;

choose_b:
	ped_geometry_destroy (a);
	return b;
}

/* copied from disk_dos */
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;
}

/*
 * internal is either the primary or extented constraint.
 * If there's no BSL, the is the only internal constraint considered.
 * If there's a BSL, try to fit the partition before or after (and
 * choose the best fit, the one which results in the greatest size...)
 */
static int
atr_prim_align (PedPartition* part, const PedConstraint* constraint,
		PedConstraint* internal)
{
	PedDevice*	dev;
	AtariDisk*	atr_disk;
	PedConstraint*	cut;
	PedGeometry*	solution = NULL;
	PedGeometry	max;
	PedSector	bsl_end;

	PED_ASSERT (part->disk != NULL);
	PED_ASSERT (part->disk->dev != NULL);
	dev = part->disk->dev;
	atr_disk = ATARI_DISK (part->disk);
	PED_ASSERT (atr_disk != NULL);

	/* No BSL */
	if (!atr_disk->bsl_start && !atr_disk->bsl_count) {
		/* Note: _ped_partition_attempt_align will destroy internal */
		return _ped_partition_attempt_align(part, constraint, internal);
	}

	/* BSL, try to fit before */
	if (atr_disk->bsl_start > 1) {
		ped_geometry_init (&max, dev, 1, atr_disk->bsl_start - 1);
		cut = ped_constraint_new_from_max (&max);
		solution = _best_solution (solution,
				_try_constraint (part, constraint,
				     ped_constraint_intersect (internal, cut)));
		ped_constraint_destroy (cut);
	}

	/* BSL, try to fit after, take the best solution */
	bsl_end = atr_disk->bsl_start + atr_disk->bsl_count;
	if (bsl_end < dev->length) {
		ped_geometry_init (&max, dev, bsl_end, dev->length - bsl_end);
		cut = ped_constraint_new_from_max (&max);
		solution = _best_solution (solution,
				_try_constraint (part, constraint,
				     ped_constraint_intersect (internal, cut)));
		ped_constraint_destroy (cut);
	}

	ped_constraint_destroy (internal);

	if (solution) {
		ped_geometry_set (&part->geom, solution->start,
				  solution->length);
		ped_geometry_destroy (solution);
		return 1;
	}

	return 0;
}

static int
atari_partition_align (PedPartition* part, const PedConstraint* constraint)
{
	PED_ASSERT (part != NULL);

	switch (part->type) {
	    case PED_PARTITION_LOGICAL:
		if (_ped_partition_attempt_align (part, constraint,
						  atr_log_constraint (part) ) )
			return 1;
		break;
	    case PED_PARTITION_EXTENDED:
		if (atr_prim_align (part, constraint,
				    atr_ext_constraint (part) ) )
			return 1;
		break;
	    default:
		if (atr_prim_align (part, constraint,
				    atr_prim_constraint (part) ) )
			return 1;
		break;
	}

#ifndef DISCOVER_ONLY
	ped_exception_throw (
		PED_EXCEPTION_ERROR,
		PED_EXCEPTION_CANCEL,
		_("Unable to satisfy all constraints on the partition."));
#endif
	return 0;
}

/* increment numbers of any non logical partition found after the last */
/* logical one, to make room for a new logical partition */
static int
art_room_for_logic (PedDisk* disk)
{
	PedPartition*	part;
	int		num, last_logic, last;

	/* too many partitions ? */
	last = ped_disk_get_last_partition_num (disk);
	if (last >= MAXIMUM_PARTS)
		return 0;

	/* find the last logical partition */
	last_logic = 0;
	for (num = 1; num <= last; num++) {
		part = ped_disk_get_partition (disk, num);
		if (part && ped_partition_is_active (part)
		         && (part->type & PED_PARTITION_LOGICAL))
			last_logic = num;
	}

	if (!last_logic)
		return 1;

	/* increment */
	for (num = last; num > last_logic; num--) {
		part = ped_disk_get_partition (disk, num);
		if (part && ped_partition_is_active (part)
		         && !(part->type & ( PED_PARTITION_LOGICAL
					   | PED_PARTITION_EXTENDED))
			 && part->num > 0 )
			part->num++;
	}

	return 1;
}

static int
atari_partition_enumerate (PedPartition* part)
{
	AtariDisk*	atrdisk;
	PedPartition*	ext_part;
	PedPartition*	log;
	int		i, want_icd, want_xgm, num_max, xgm_begin, prim_count;

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

	/* WARNING: some similar/related code in atari_write */
	/* This is quite a <hack> : this function is probably the only way   */
	/* to know something has been / is going to be modified in the table.*/
	/* So we detect the current operation mode (AHDI/XGM/ICD) and report */
	/* errors (in which case we refuse to operate...) */

	prim_count = ped_disk_get_primary_partition_count (part->disk);
	ext_part = ped_disk_extended_partition (part->disk);

	/* <hack in the hack> : we can't reorder (yet) , so if we begin with */
	/* XGM the first slot must be empty */
	xgm_begin = ((log = ped_disk_get_partition (part->disk, 1))
		      && (log->type & PED_PARTITION_LOGICAL))
		    || ((part->num == -1)
		        && (part->type & PED_PARTITION_LOGICAL)
			&& !ped_disk_get_partition (part->disk, 1));
	/* </hack in the hack> */

	PED_ASSERT (atrdisk->format != FMT_ICD || ext_part == NULL);
	PED_ASSERT (atrdisk->format != FMT_XGM
		    || prim_count + xgm_begin <= N_AHDI);
	PED_ASSERT (atrdisk->format != FMT_AHDI
		    || (ext_part == NULL && prim_count + xgm_begin <= N_AHDI));

	want_icd = ( ( prim_count
			+ xgm_begin
			+ ( (part->num == -1)
			    && !(part->type & PED_PARTITION_LOGICAL) ) )
		      > N_AHDI );
	want_xgm = ( (part->type & PED_PARTITION_EXTENDED)
	             || ext_part != NULL );

	if (!want_xgm && !want_icd)
		atrdisk->format = FMT_AHDI;
	else if (want_xgm && !want_icd)
		atrdisk->format = FMT_XGM;
	else if (!want_xgm && want_icd)
		atrdisk->format = FMT_ICD;
	else {
		if (atr_xgm_in_icd (part->disk, PED_PARTITION_EXTENDED))
			return 0;
		else {
			ped_exception_throw (
				PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
			      _("You can't use more than %d primary partitions "
			        "(ICD mode) if you use an extended XGM "
				"partition.  If XGM is the first partition "
				"it counts for two."),
				N_AHDI );
			return 0;
		}
	}
	/* End of </hack> */


	/* Ext will be numbered 0 and will stay 0... */
	if (part->num == 0)
		return 1;

	if (part->num == -1) {

		/* Linux don't show the ext part itself for Atari disk labels */
		/* so we use number 0 (could use a big number too, but that   */
		/* would be less cute ;) */
		if (part->type & PED_PARTITION_EXTENDED) {
			part->num = 0;
			return 1;
		}

		switch (atrdisk->format) {
		    case FMT_AHDI:
		    case FMT_ICD:
			num_max = N_ICD + N_AHDI;
			break;
		    case FMT_XGM:
			num_max = MAXIMUM_PARTS;
			break;
		    default:
			num_max = 0;
			PED_ASSERT (0);
		}

		/* make room for logical partitions */
		if (part->type & PED_PARTITION_LOGICAL) {
			if (!art_room_for_logic (part->disk))
				goto error_alloc_failed;
		}

		/* find an unused number */
		for (i = 1; i <= num_max; i++) {
			if (!ped_disk_get_partition (part->disk, i)) {
				part->num = i;
				return 1;
			}
		}

	} else {
		/* find an unused number before or don't re-number */
		for (i = 1; i < part->num; i++) {
			if (!ped_disk_get_partition (part->disk, i)) {
				part->num = i;
			}
		}
		return 1;
	}

	/* failed to allocate a number */
error_alloc_failed:
#ifndef DISCOVER_ONLY
	ped_exception_throw (PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL,
		_("Unable to allocate a partition number."));
#endif
	return 0;
}

static int
atr_creat_add_metadata (PedDisk* disk, PedSector start, PedSector end,
			PedPartitionType type )
{
	PedPartition*	new_part;
	PedConstraint*	const_exact;
	int		added;

	type |= PED_PARTITION_METADATA;
	new_part = ped_partition_new (disk, type, NULL, start, end);
	if (!new_part)
		goto error;

	const_exact = ped_constraint_exact (&new_part->geom);
	added = ped_disk_add_partition (disk, new_part, const_exact);
	ped_constraint_destroy (const_exact);
	if (!added)
		goto error_destroy_part;

	return 1;

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

static int
atari_alloc_metadata (PedDisk* disk)
{
	PedPartition*	ext;
	PedPartition*	log;
	AtariDisk*	atr_disk;
	int		i;

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

	/* allocate 1 sector for the disk label at the start */
	if (!atr_creat_add_metadata (disk, 0, 0, 0))
		return 0;

	/* allocate the sectors containing the BSL */
	if (atr_disk->bsl_start || atr_disk->bsl_count) {
		if (!atr_creat_add_metadata (disk, atr_disk->bsl_start,
					     atr_disk->bsl_start
					      + atr_disk->bsl_count - 1, 0 ) )
			return 0;
	}

	ext = ped_disk_extended_partition (disk);
	if (ext) {
		if (!atr_creat_add_metadata (disk, ext->geom.start,
					     ext->geom.start,
					     PED_PARTITION_LOGICAL ) )
			return 0;

		/* Find the first logical part */
		for (i = 1; i <= ped_disk_get_last_partition_num (disk); i++)
			if ((log = ped_disk_get_partition (disk, i))
			    && (log->type & PED_PARTITION_LOGICAL))
				break;

		for (log = ext->part_list; log; log = log->next) {
			if ((log->type & ( PED_PARTITION_METADATA
					 | PED_PARTITION_FREESPACE))
			    || log->num == i)
				continue;

			if (!atr_creat_add_metadata (disk, log->geom.start-1,
						     log->geom.start-1,
						     PED_PARTITION_LOGICAL ) )
				return 0;
		}
	}

	return 1;
}

static int _GL_ATTRIBUTE_PURE
atari_get_max_primary_partition_count (const PedDisk* disk)
{
	AtariDisk*	atr_disk;

	PED_ASSERT (disk != NULL);
	atr_disk = ATARI_DISK (disk);
	PED_ASSERT (atr_disk != NULL);

	return atr_disk->format == FMT_XGM ? N_AHDI : N_AHDI + N_ICD;
}

static bool
atari_get_max_supported_partition_count (const PedDisk* disk, int *max_n)
{
	AtariDisk*	atr_disk;

	PED_ASSERT (disk != NULL);
	atr_disk = ATARI_DISK (disk);
	PED_ASSERT (atr_disk != NULL);

	*max_n = atr_disk->format == FMT_XGM ? N_AHDI : N_AHDI + N_ICD;
        return true;
}

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

static PedDiskOps atari_disk_ops = {
	clobber:	        NULL_IF_DISCOVER_ONLY (atari_clobber),
	write:			NULL_IF_DISCOVER_ONLY (atari_write),

	partition_set_name:	NULL,
	partition_get_name:	NULL,

        PT_op_function_initializers (atari)
};

static PedDiskType atari_disk_type = {
	next:		NULL,
	name:		"atari",
	ops:		&atari_disk_ops,
	features:	PED_DISK_TYPE_EXTENDED
};

void
ped_disk_atari_init ()
{
	PED_ASSERT (sizeof (AtariRawPartition) == 12);
	PED_ASSERT (sizeof (AtariRawTable) == 512);
	/* GNU Libc doesn't support NULL instead of the locale name */
	PED_ASSERT ((atr_c_locale = newlocale(LC_ALL_MASK, "C", NULL)) != NULL);

	ped_disk_type_register (&atari_disk_type);
}

void
ped_disk_atari_done ()
{
	ped_disk_type_unregister (&atari_disk_type);
	freelocale(atr_c_locale);
}
