/*
 * Copyright (C) 2007-2013 Karel Zak <kzak@redhat.com>
 *
 * Based on the original code from fdisk
 *    written by Bernhard Fastenrath (fasten@informatik.uni-bonn.de)
 *    with code from the NetBSD disklabel command.
 *
 *    Arnaldo Carvalho de Melo <acme@conectiva.com.br>, March 1999
 *    David Huggins-Daines <dhuggins@linuxcare.com>, January 2000
 */
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/param.h>

#include "nls.h"
#include "blkdev.h"
#include "fdiskP.h"
#include "pt-mbr.h"
#include "pt-bsd.h"
#include "all-io.h"


/**
 * SECTION: bsd
 * @title: BSD
 * @short_description: disk label specific functions
 *
 */

static const char *bsd_dktypenames[] = {
	"unknown",
	"SMD",
	"MSCP",
	"old DEC",
	"SCSI",
	"ESDI",
	"ST506",
	"HP-IB",
	"HP-FL",
	"type 9",
	"floppy",
	0
};
#define BSD_DKMAXTYPES	(ARRAY_SIZE(bsd_dktypenames) - 1)

static struct fdisk_parttype bsd_fstypes[] = {
        {BSD_FS_UNUSED, "unused"},
	{BSD_FS_SWAP,   "swap"},
	{BSD_FS_V6,     "Version 6"},
	{BSD_FS_V7,     "Version 7"},
	{BSD_FS_SYSV,   "System V"},
	{BSD_FS_V71K,   "4.1BSD"},
	{BSD_FS_V8,     "Eighth Edition"},
	{BSD_FS_BSDFFS, "4.2BSD"},
#ifdef __alpha__
	{BSD_FS_EXT2,   "ext2"},
#else
	{BSD_FS_MSDOS,  "MS-DOS"},
#endif
	{BSD_FS_BSDLFS, "4.4LFS"},
	{BSD_FS_OTHER,  "unknown"},
	{BSD_FS_HPFS,   "HPFS"},
	{BSD_FS_ISO9660,"ISO-9660"},
	{BSD_FS_BOOT,   "boot"},
	{BSD_FS_ADOS,   "ADOS"},
	{BSD_FS_HFS,    "HFS"},
	{BSD_FS_ADVFS,	"AdvFS"},
	{ 0, NULL }
};
#define BSD_FSMAXTYPES (ARRAY_SIZE(bsd_fstypes)-1)

/*
 * in-memory fdisk BSD stuff
 */
struct fdisk_bsd_label {
	struct fdisk_label	head;		/* generic part */

	struct dos_partition *dos_part;		/* parent */
	struct bsd_disklabel bsd;		/* on disk label */
#if defined (__alpha__)
	/* We access this through a u_int64_t * when checksumming */
	char bsdbuffer[BSD_BBSIZE] __attribute__((aligned(8)));
#else
	char bsdbuffer[BSD_BBSIZE];
#endif
};

static int bsd_initlabel(struct fdisk_context *cxt);
static int bsd_readlabel(struct fdisk_context *cxt);
static void sync_disks(struct fdisk_context *cxt);

static inline struct fdisk_bsd_label *self_label(struct fdisk_context *cxt)
{
	assert(cxt);
	assert(cxt->label);
	assert(fdisk_is_label(cxt, BSD));

	return (struct fdisk_bsd_label *) cxt->label;
}

static inline struct bsd_disklabel *self_disklabel(struct fdisk_context *cxt)
{
	assert(cxt);
	assert(cxt->label);
	assert(fdisk_is_label(cxt, BSD));

	return &((struct fdisk_bsd_label *) cxt->label)->bsd;
}

static struct fdisk_parttype *bsd_partition_parttype(
		struct fdisk_context *cxt,
		struct bsd_partition *p)
{
	struct fdisk_parttype *t
		= fdisk_label_get_parttype_from_code(cxt->label, p->p_fstype);
	return t ? : fdisk_new_unknown_parttype(p->p_fstype, NULL);
}


#if defined (__alpha__)
static void alpha_bootblock_checksum (char *boot)
{
	uint64_t *dp = (uint64_t *) boot, sum = 0;
	int i;

	for (i = 0; i < 63; i++)
		sum += dp[i];
	dp[63] = sum;
}
#endif /* __alpha__ */

#define HIDDEN_MASK	0x10

static int is_bsd_partition_type(int type)
{
	return (type == MBR_FREEBSD_PARTITION ||
		type == (MBR_FREEBSD_PARTITION ^ HIDDEN_MASK) ||
		type == MBR_NETBSD_PARTITION ||
		type == (MBR_NETBSD_PARTITION ^ HIDDEN_MASK) ||
		type == MBR_OPENBSD_PARTITION ||
		type == (MBR_OPENBSD_PARTITION ^ HIDDEN_MASK));
}

/*
 * look for DOS partition usable for nested BSD partition table
 */
static int bsd_assign_dos_partition(struct fdisk_context *cxt)
{
	struct fdisk_bsd_label *l = self_label(cxt);
	size_t i;

	for (i = 0; i < 4; i++) {
		fdisk_sector_t ss;

		l->dos_part = fdisk_dos_get_partition(cxt->parent, i);

		if (!l->dos_part || !is_bsd_partition_type(l->dos_part->sys_ind))
			continue;

		ss = dos_partition_get_start(l->dos_part);
		if (!ss) {
			fdisk_warnx(cxt, _("Partition %zd: has invalid starting "
					   "sector 0."), i + 1);
			return -1;
		}

		if (cxt->parent->dev_path) {
			free(cxt->dev_path);
			cxt->dev_path = fdisk_partname(
						cxt->parent->dev_path, i + 1);
		}

		DBG(LABEL, ul_debug("partition %zu assigned to BSD", i + 1));
		return 0;
	}

	fdisk_warnx(cxt, _("There is no *BSD partition on %s."),
				cxt->parent->dev_path);
	free(cxt->dev_path);
	cxt->dev_path = NULL;
	l->dos_part = NULL;
	return 1;
}

static int bsd_probe_label(struct fdisk_context *cxt)
{
	int rc = 0;

	if (cxt->parent)
		rc = bsd_assign_dos_partition(cxt);	/* nested BSD partiotn table */
	if (!rc)
		rc = bsd_readlabel(cxt);
	if (!rc)
		return 1;	/* found BSD */
	return 0;		/* not found */
}

static int set_parttype(
		struct fdisk_context *cxt,
		size_t partnum,
		struct fdisk_parttype *t)
{
	struct bsd_partition *p;
	struct bsd_disklabel *d = self_disklabel(cxt);

	if (partnum >= d->d_npartitions || !t || t->code > UINT8_MAX)
		return -EINVAL;

	p = &d->d_partitions[partnum];
	if (t->code == p->p_fstype)
		return 0;

	p->p_fstype = t->code;
	fdisk_label_set_changed(cxt->label, 1);
	return 0;
}

static int bsd_add_partition(struct fdisk_context *cxt,
			     struct fdisk_partition *pa,
			     size_t *partno)
{
	struct fdisk_bsd_label *l = self_label(cxt);
	struct bsd_disklabel *d = self_disklabel(cxt);
	size_t i;
	unsigned int begin = 0, end;
	int rc = 0;

	rc = fdisk_partition_next_partno(pa, cxt, &i);
	if (rc)
		return rc;
	if (i >= BSD_MAXPARTITIONS)
		return -ERANGE;
	if (l->dos_part) {
		begin = dos_partition_get_start(l->dos_part);
		end = begin + dos_partition_get_size(l->dos_part) - 1;
	} else
		end = d->d_secperunit - 1;

	/*
	 * First sector
	 */
	if (pa && pa->start_follow_default)
		;
	else if (pa && fdisk_partition_has_start(pa)) {
		if (pa->start < begin || pa->start > end)
			return -ERANGE;
		begin = pa->start;
	} else {
		struct fdisk_ask *ask = fdisk_new_ask();

		if (!ask)
			return -ENOMEM;
		fdisk_ask_set_query(ask,
			fdisk_use_cylinders(cxt) ?
			_("First cylinder") : _("First sector"));
		fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
		fdisk_ask_number_set_low(ask, fdisk_cround(cxt, begin));
		fdisk_ask_number_set_default(ask, fdisk_cround(cxt, begin));
		fdisk_ask_number_set_high(ask, fdisk_cround(cxt, end));

		rc = fdisk_do_ask(cxt, ask);
		begin = fdisk_ask_number_get_result(ask);
		fdisk_unref_ask(ask);
		if (rc)
			return rc;
		if (fdisk_use_cylinders(cxt))
			begin = (begin - 1) * d->d_secpercyl;
	}

	/*
	 * Last sector
	 */
	if (pa && pa->end_follow_default)
		;
	else if (pa && fdisk_partition_has_size(pa)) {
		if (begin + pa->size > end)
			return -ERANGE;
		end = begin + pa->size - 1ULL;
	} else {
		/* ask user by dialog */
		struct fdisk_ask *ask = fdisk_new_ask();

		if (!ask)
			return -ENOMEM;
		fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET);

		if (fdisk_use_cylinders(cxt)) {
			fdisk_ask_set_query(ask, _("Last cylinder, +cylinders or +size{K,M,G,T,P}"));
			fdisk_ask_number_set_unit(ask,
				     cxt->sector_size *
				     fdisk_get_units_per_sector(cxt));
		} else {
			fdisk_ask_set_query(ask, _("Last sector, +sectors or +size{K,M,G,T,P}"));
			fdisk_ask_number_set_unit(ask,cxt->sector_size);
		}

		fdisk_ask_number_set_low(ask, fdisk_cround(cxt, begin));
		fdisk_ask_number_set_default(ask, fdisk_cround(cxt, end));
		fdisk_ask_number_set_high(ask, fdisk_cround(cxt, end));
		fdisk_ask_number_set_base(ask, fdisk_cround(cxt, begin));

		rc = fdisk_do_ask(cxt, ask);
		end = fdisk_ask_number_get_result(ask);
		fdisk_unref_ask(ask);
		if (rc)
			return rc;
		if (fdisk_use_cylinders(cxt))
			end = end * d->d_secpercyl - 1;
	}

	d->d_partitions[i].p_size   = end - begin + 1;
	d->d_partitions[i].p_offset = begin;
	d->d_partitions[i].p_fstype = BSD_FS_UNUSED;

	if (i >= d->d_npartitions)
		d->d_npartitions = i + 1;
	cxt->label->nparts_cur = d->d_npartitions;

	if (pa && pa->type)
		set_parttype(cxt, i, pa->type);

	fdisk_label_set_changed(cxt->label, 1);
	if (partno)
		*partno = i;
	return 0;
}

static int bsd_set_partition(struct fdisk_context *cxt, size_t n,
			     struct fdisk_partition *pa)
{
	struct bsd_partition *p;
	struct fdisk_bsd_label *l = self_label(cxt);
	struct bsd_disklabel *d = self_disklabel(cxt);

	if (n >= d->d_npartitions)
		return -EINVAL;

	p = &d->d_partitions[n];

	/* we have to stay within parental DOS partition */
	if (l->dos_part && (fdisk_partition_has_start(pa) ||
			    fdisk_partition_has_size(pa))) {

		fdisk_sector_t dosbegin = dos_partition_get_start(l->dos_part);
		fdisk_sector_t dosend = dosbegin + dos_partition_get_size(l->dos_part) - 1;
		fdisk_sector_t begin = fdisk_partition_has_start(pa) ? pa->start : p->p_offset;
		fdisk_sector_t end = begin + (fdisk_partition_has_size(pa) ? pa->size : p->p_size) - 1;

		if (begin < dosbegin || begin > dosend)
			return -ERANGE;
		if (end < dosbegin || end > dosend)
			return -ERANGE;
	}

	if (pa->type) {
		int rc = set_parttype(cxt, n, pa->type);
		if (rc)
			return rc;
	}

	if (fdisk_partition_has_start(pa))
		d->d_partitions[n].p_offset = pa->start;
	if (fdisk_partition_has_size(pa))
		d->d_partitions[n].p_size = pa->size;

	fdisk_label_set_changed(cxt->label, 1);
	return 0;
}


/* Returns 0 on success, < 0 on error. */
static int bsd_create_disklabel(struct fdisk_context *cxt)
{
	int rc, yes = 0;
	struct bsd_disklabel *d = self_disklabel(cxt);

	fdisk_info(cxt, _("The device %s does not contain BSD disklabel."), cxt->dev_path);
	rc = fdisk_ask_yesno(cxt,
			_("Do you want to create a BSD disklabel?"),
			&yes);
	if (rc)
		return rc;
	if (!yes)
		return 1;
	if (cxt->parent) {
		rc = bsd_assign_dos_partition(cxt);
		if (rc == 1)
			/* not found DOS partition usable for BSD label */
			rc = -EINVAL;
	}
	if (rc)
		return rc;

	rc = bsd_initlabel(cxt);
	if (!rc) {
		cxt->label->nparts_cur = d->d_npartitions;
		cxt->label->nparts_max = BSD_MAXPARTITIONS;
	}

	return rc;
}

static int bsd_delete_part(
		struct fdisk_context *cxt,
		size_t partnum)
{
	struct bsd_disklabel *d = self_disklabel(cxt);

	d->d_partitions[partnum].p_size   = 0;
	d->d_partitions[partnum].p_offset = 0;
	d->d_partitions[partnum].p_fstype = BSD_FS_UNUSED;

	if (d->d_npartitions == partnum + 1)
		while (!d->d_partitions[d->d_npartitions - 1].p_size)
			d->d_npartitions--;

	cxt->label->nparts_cur = d->d_npartitions;
	fdisk_label_set_changed(cxt->label, 1);
	return 0;
}

static int bsd_get_disklabel_item(struct fdisk_context *cxt, struct fdisk_labelitem *item)
{
	struct bsd_disklabel *d;
	int rc = 0;

	assert(cxt);
	assert(cxt->label);
	assert(fdisk_is_label(cxt, BSD));

	d = self_disklabel(cxt);

	switch (item->id) {
	case BSD_LABELITEM_TYPE:
		item->name = _("Type");
		item->type = 's';
		if ((unsigned) d->d_type < BSD_DKMAXTYPES) {
			item->data.str = strdup(bsd_dktypenames[d->d_type]);
			if (!item->data.str)
				rc = -ENOMEM;
		} else if (asprintf(&item->data.str, "%d", d->d_type) < 0)
			rc = -ENOMEM;
		break;
	case BSD_LABELITEM_DISK:
		item->name = _("Disk");
		item->type = 's';
		item->data.str = strndup(d->d_typename, sizeof(d->d_typename));
		if (!item->data.str)
			rc = -ENOMEM;
		break;
	case BSD_LABELITEM_PACKNAME:
		item->name = _("Packname");
		item->type = 's';
		item->data.str = strndup(d->d_packname, sizeof(d->d_packname));
		if (!item->data.str)
			rc = -ENOMEM;
		break;
	case BSD_LABELITEM_FLAGS:
		item->name = _("Flags");
		item->type = 's';
		item->data.str = strdup(
			d->d_flags & BSD_D_REMOVABLE ? _(" removable") :
			d->d_flags & BSD_D_ECC ? _(" ecc") :
			d->d_flags & BSD_D_BADSECT ? _(" badsect") : "");
		if (!item->data.str)
			rc = -ENOMEM;
		break;

	/* On various machines the fields of *lp are short/int/long */
	/* In order to avoid problems, we cast them all uint64. */
	case BSD_LABELITEM_SECSIZE:
		item->name = _("Bytes/Sector");
		item->type = 'j';
		item->data.num64 = (uint64_t) d->d_secsize;
		break;
	case BSD_LABELITEM_NTRACKS:
		item->name = _("Tracks/Cylinder");
		item->type = 'j';
		item->data.num64 = (uint64_t) d->d_ntracks;
		break;
	case BSD_LABELITEM_SECPERCYL:
		item->name = _("Sectors/Cylinder");
		item->data.num64 = (uint64_t) d->d_secpercyl;
		item->type = 'j';
		break;
	case BSD_LABELITEM_CYLINDERS:
		item->name = _("Cylinders");
		item->data.num64 = (uint64_t) d->d_ncylinders;
		item->type = 'j';
		break;
	case BSD_LABELITEM_RPM:
		item->name = _("Rpm");
		item->data.num64 = (uint64_t) d->d_rpm;
		item->type = 'j';
		break;
	case BSD_LABELITEM_INTERLEAVE:
		item->name = _("Interleave");
		item->data.num64 = (uint64_t) d->d_interleave;
		item->type = 'j';
		break;
	case BSD_LABELITEM_TRACKSKEW:
		item->name = _("Trackskew");
		item->data.num64 = (uint64_t) d->d_trackskew;
		item->type = 'j';
		break;
	case BSD_LABELITEM_CYLINDERSKEW:
		item->name = _("Cylinderskew");
		item->data.num64 = (uint64_t) d->d_cylskew;
		item->type = 'j';
		break;
	case BSD_LABELITEM_HEADSWITCH:
		item->name = _("Headswitch");
		item->data.num64 = (uint64_t) d->d_headswitch;
		item->type = 'j';
		break;
	case BSD_LABELITEM_TRKSEEK:
		item->name = _("Track-to-track seek");
		item->data.num64 = (uint64_t) d->d_trkseek;
		item->type = 'j';
		break;
	default:
		if (item->id < __FDISK_NLABELITEMS)
			rc = 1;	/* unssupported generic item */
		else
			rc = 2;	/* out of range */
		break;
	}

	return rc;
}

static int bsd_get_partition(struct fdisk_context *cxt, size_t n,
			     struct fdisk_partition *pa)
{
	struct bsd_partition *p;
	struct bsd_disklabel *d = self_disklabel(cxt);

	assert(cxt);
	assert(cxt->label);
	assert(fdisk_is_label(cxt, BSD));

	if (n >= d->d_npartitions)
		return -EINVAL;

	p = &d->d_partitions[n];

	pa->used = p->p_size ? 1 : 0;
	if (!pa->used)
		return 0;

	if (fdisk_use_cylinders(cxt) && d->d_secpercyl) {
		pa->start_post = p->p_offset % d->d_secpercyl ? '*' : ' ';
		pa->end_post = (p->p_offset + p->p_size) % d->d_secpercyl ? '*' : ' ';
	}

	pa->start = p->p_offset;
	pa->size = p->p_size;
	pa->type = bsd_partition_parttype(cxt, p);

	if (p->p_fstype == BSD_FS_UNUSED || p->p_fstype == BSD_FS_BSDFFS) {
		pa->fsize = p->p_fsize;
		pa->bsize = p->p_fsize * p->p_frag;
	}
	if (p->p_fstype == BSD_FS_BSDFFS)
		pa->cpg = p->p_cpg;

	return 0;
}

static uint32_t ask_uint32(struct fdisk_context *cxt,
		uint32_t dflt, char *mesg)
{
	uintmax_t res;

	if (fdisk_ask_number(cxt, min(dflt, (uint32_t) 1), dflt,
				UINT32_MAX, mesg, &res) == 0)
		return res;
	return dflt;
}

static uint16_t ask_uint16(struct fdisk_context *cxt,
		uint16_t dflt, char *mesg)
{
	uintmax_t res;

	if (fdisk_ask_number(cxt, min(dflt, (uint16_t) 1),
				dflt, UINT16_MAX, mesg, &res) == 0)
		return res;
	return dflt;
}

/**
 * fdisk_bsd_edit_disklabel:
 * @cxt: context
 *
 * Edits fields in BSD disk label.
 *
 * Returns: 0 on success, <0 on error
 */
int fdisk_bsd_edit_disklabel(struct fdisk_context *cxt)
{
	struct bsd_disklabel *d = self_disklabel(cxt);
	uintmax_t res;

#if defined (__alpha__) || defined (__ia64__)
	if (fdisk_ask_number(cxt, DEFAULT_SECTOR_SIZE, d->d_secsize,
			     UINT32_MAX, _("bytes/sector"), &res) == 0)
		d->d_secsize = res;

	d->d_nsectors = ask_uint32(cxt, d->d_nsectors, _("sectors/track"));
	d->d_ntracks = ask_uint32(cxt, d->d_ntracks, _("tracks/cylinder"));
	d->d_ncylinders = ask_uint32(cxt, d->d_ncylinders  ,_("cylinders"));
#endif
	if (fdisk_ask_number(cxt, 1, d->d_nsectors * d->d_ntracks,
			     d->d_nsectors * d->d_ntracks,
			     _("sectors/cylinder"), &res) == 0)
		d->d_secpercyl = res;

	d->d_rpm = ask_uint16(cxt, d->d_rpm, _("rpm"));
	d->d_interleave = ask_uint16(cxt, d->d_interleave, _("interleave"));
	d->d_trackskew = ask_uint16(cxt, d->d_trackskew, _("trackskew"));
	d->d_cylskew = ask_uint16(cxt, d->d_cylskew, _("cylinderskew"));

	d->d_headswitch = ask_uint32(cxt, d->d_headswitch, _("headswitch"));
	d->d_trkseek = ask_uint32(cxt, d->d_trkseek, _("track-to-track seek"));

	d->d_secperunit = d->d_secpercyl * d->d_ncylinders;
	return 0;
}

static int bsd_get_bootstrap(struct fdisk_context *cxt,
			char *path, void *ptr, int size)
{
	int fd;

	if ((fd = open(path, O_RDONLY)) < 0) {
		fdisk_warn(cxt, _("cannot open %s"), path);
		return -errno;
	}

	if (read_all(fd, ptr, size) != size) {
		fdisk_warn(cxt, _("cannot read %s"), path);
		close(fd);
		return -errno;
	}

	fdisk_info(cxt, _("The bootstrap file %s successfully loaded."), path);
	close (fd);
	return 0;
}

/**
 * fdisk_bsd_write_bootstrap:
 * @cxt: context
 *
 * Install bootstrap file to the BSD device
 */
int fdisk_bsd_write_bootstrap(struct fdisk_context *cxt)
{
	struct bsd_disklabel dl, *d = self_disklabel(cxt);
	struct fdisk_bsd_label *l = self_label(cxt);
	char *name = d->d_type == BSD_DTYPE_SCSI ? "sd" : "wd";
	char buf[BUFSIZ];
	char *res, *dp, *p;
	int rc;
	fdisk_sector_t sector;

	snprintf(buf, sizeof(buf),
		_("Bootstrap: %1$sboot -> boot%1$s (default %1$s)"),
		name);
	rc = fdisk_ask_string(cxt, buf, &res);
	if (rc)
		goto done;
	if (res && *res)
		name = res;

	snprintf(buf, sizeof(buf), "%s/%sboot", BSD_LINUX_BOOTDIR, name);
	rc = bsd_get_bootstrap(cxt, buf, l->bsdbuffer,	(int) d->d_secsize);
	if (rc)
		goto done;

	/* We need a backup of the disklabel (might have changed). */
	dp = &l->bsdbuffer[BSD_LABELSECTOR * DEFAULT_SECTOR_SIZE];
	memmove(&dl, dp, sizeof(struct bsd_disklabel));

	/* The disklabel will be overwritten by 0's from bootxx anyway */
	memset(dp, 0, sizeof(struct bsd_disklabel));

	snprintf(buf, sizeof(buf), "%s/boot%s", BSD_LINUX_BOOTDIR, name);
	rc = bsd_get_bootstrap(cxt, buf,
			&l->bsdbuffer[d->d_secsize],
			(int) d->d_bbsize - d->d_secsize);
	if (rc)
		goto done;

	/* check end of the bootstrap */
	for (p = dp; p < dp + sizeof(struct bsd_disklabel); p++) {
		if (!*p)
			continue;
		fdisk_warnx(cxt, _("Bootstrap overlaps with disklabel!"));
		return -EINVAL;
	}

	/* move disklabel back */
	memmove(dp, &dl, sizeof(struct bsd_disklabel));

	sector = 0;
	if (l->dos_part)
		sector = dos_partition_get_start(l->dos_part);
#if defined (__alpha__)
	alpha_bootblock_checksum(l->bsdbuffer);
#endif
	if (lseek(cxt->dev_fd, (off_t) sector * DEFAULT_SECTOR_SIZE, SEEK_SET) == -1) {
		fdisk_warn(cxt, _("seek on %s failed"), cxt->dev_path);
		rc = -errno;
		goto done;
	}
	if (write_all(cxt->dev_fd, l->bsdbuffer, BSD_BBSIZE)) {
		fdisk_warn(cxt, _("cannot write %s"), cxt->dev_path);
		rc = -errno;
		goto done;
	}

	fdisk_info(cxt, _("Bootstrap installed on %s."), cxt->dev_path);
	sync_disks(cxt);

	rc = 0;
done:
	free(res);
	return rc;
}

static unsigned short bsd_dkcksum (struct bsd_disklabel *lp)
{
	unsigned short *start, *end;
	unsigned short sum = 0;

	start = (unsigned short *) lp;
	end = (unsigned short *) &lp->d_partitions[lp->d_npartitions];
	while (start < end)
		sum ^= *start++;
	return sum;
}

static int bsd_initlabel (struct fdisk_context *cxt)
{
	struct fdisk_bsd_label *l = self_label(cxt);
	struct bsd_disklabel *d = self_disklabel(cxt);
	struct bsd_partition *pp;

	memset (d, 0, sizeof (struct bsd_disklabel));

	d -> d_magic = BSD_DISKMAGIC;

	if (strncmp (cxt->dev_path, "/dev/sd", 7) == 0)
		d -> d_type = BSD_DTYPE_SCSI;
	else
		d -> d_type = BSD_DTYPE_ST506;

#if !defined (__alpha__)
	d -> d_flags = BSD_D_DOSPART;
#else
	d -> d_flags = 0;
#endif
	d -> d_secsize = DEFAULT_SECTOR_SIZE;		/* bytes/sector  */
	d -> d_nsectors = cxt->geom.sectors;		/* sectors/track */
	d -> d_ntracks = cxt->geom.heads;		/* tracks/cylinder (heads) */
	d -> d_ncylinders = cxt->geom.cylinders;
	d -> d_secpercyl  = cxt->geom.sectors * cxt->geom.heads;/* sectors/cylinder */
	if (d -> d_secpercyl == 0)
		d -> d_secpercyl = 1;		/* avoid segfaults */
	d -> d_secperunit = d -> d_secpercyl * d -> d_ncylinders;

	d -> d_rpm = 3600;
	d -> d_interleave = 1;
	d -> d_trackskew = 0;
	d -> d_cylskew = 0;
	d -> d_headswitch = 0;
	d -> d_trkseek = 0;

	d -> d_magic2 = BSD_DISKMAGIC;
	d -> d_bbsize = BSD_BBSIZE;
	d -> d_sbsize = BSD_SBSIZE;

	if (l->dos_part) {
		d->d_npartitions = 4;

		pp = &d->d_partitions[2];	/* Partition C should be the NetBSD partition */
		pp->p_offset = dos_partition_get_start(l->dos_part);
		pp->p_size   = dos_partition_get_size(l->dos_part);
		pp->p_fstype = BSD_FS_UNUSED;

		pp = &d -> d_partitions[3];	/* Partition D should be the whole disk */
		pp->p_offset = 0;
		pp->p_size   = d->d_secperunit;
		pp->p_fstype = BSD_FS_UNUSED;
	} else {
		d->d_npartitions = 3;

		pp = &d->d_partitions[2];	/* Partition C should be the whole disk */
		pp->p_offset = 0;
		pp->p_size   = d->d_secperunit;
		pp->p_fstype = BSD_FS_UNUSED;
	}

	return 0;
}

/*
 * Read a bsd_disklabel from sector 0 or from the starting sector of p.
 * If it has the right magic, return 0.
 */
static int bsd_readlabel(struct fdisk_context *cxt)
{
	struct fdisk_bsd_label *l;
	struct bsd_disklabel *d;
	int t;
	off_t offset = 0;

	l = self_label(cxt);
	d = self_disklabel(cxt);

	if (l->dos_part)
		/* BSD is nested within DOS partition, get the begin of the
		 * partition. Note that DOS uses native sector size. */
		offset = dos_partition_get_start(l->dos_part) * cxt->sector_size;

	if (lseek(cxt->dev_fd, offset, SEEK_SET) == -1)
		return -1;
	if (read_all(cxt->dev_fd, l->bsdbuffer, sizeof(l->bsdbuffer)) < 0)
		return errno ? -errno : -1;

	/* The offset to begin of the disk label. Note that BSD uses
	 * 512-byte (default) sectors. */
	memmove(d, &l->bsdbuffer[BSD_LABELSECTOR * DEFAULT_SECTOR_SIZE
			      + BSD_LABELOFFSET], sizeof(*d));

	if (d->d_magic != BSD_DISKMAGIC || d->d_magic2 != BSD_DISKMAGIC) {
		DBG(LABEL, ul_debug("not found magic"));
		return -1;
	}

	for (t = d->d_npartitions; t < BSD_MAXPARTITIONS; t++) {
		d->d_partitions[t].p_size   = 0;
		d->d_partitions[t].p_offset = 0;
		d->d_partitions[t].p_fstype = BSD_FS_UNUSED;
	}

	if (d->d_npartitions > BSD_MAXPARTITIONS)
		fdisk_warnx(cxt, ("Too many partitions (%d, maximum is %d)."),
				d->d_npartitions, BSD_MAXPARTITIONS);

	/* let's follow in-PT geometry */
	cxt->geom.sectors = d->d_nsectors;
	cxt->geom.heads = d->d_ntracks;
	cxt->geom.cylinders = d->d_ncylinders;

	cxt->label->nparts_cur = d->d_npartitions;
	cxt->label->nparts_max = BSD_MAXPARTITIONS;
	DBG(LABEL, ul_debug("read BSD label"));
	return 0;
}

static int bsd_write_disklabel(struct fdisk_context *cxt)
{
	off_t offset = 0;
	struct fdisk_bsd_label *l = self_label(cxt);
	struct bsd_disklabel *d = self_disklabel(cxt);


	if (l->dos_part)
		offset = dos_partition_get_start(l->dos_part) * cxt->sector_size;

	d->d_checksum = 0;
	d->d_checksum = bsd_dkcksum(d);

	/* Update label within boot block. */
	memmove(&l->bsdbuffer[BSD_LABELSECTOR * DEFAULT_SECTOR_SIZE
			   + BSD_LABELOFFSET], d, sizeof(*d));

#if defined (__alpha__) && BSD_LABELSECTOR == 0
	/* Write the checksum to the end of the first sector. */
	alpha_bootblock_checksum(l->bsdbuffer);
#endif
	if (lseek(cxt->dev_fd, offset, SEEK_SET) == -1) {
		fdisk_warn(cxt, _("seek on %s failed"), cxt->dev_path);
		return -errno;
	}
	if (write_all(cxt->dev_fd, l->bsdbuffer, sizeof(l->bsdbuffer))) {
		fdisk_warn(cxt, _("cannot write %s"), cxt->dev_path);
		return -errno;
	}
	sync_disks(cxt);

	if (cxt->parent && fdisk_label_is_changed(cxt->parent->label))
		fdisk_info(cxt, _("Disklabel written to %s (don't forget to write %s disklable too)."),
				cxt->dev_path, cxt->parent->dev_path);
	else
		fdisk_info(cxt, _("Disklabel written to %s."), cxt->dev_path);
	return 0;
}

static void sync_disks(struct fdisk_context *cxt)
{
	fdisk_info(cxt, _("Syncing disks."));
	sync();
}

static int bsd_translate_fstype (int linux_type)
{
	switch (linux_type) {
	case 0x01: /* DOS 12-bit FAT   */
	case 0x04: /* DOS 16-bit <32M  */
	case 0x06: /* DOS 16-bit >=32M */
	case 0xe1: /* DOS access       */
	case 0xe3: /* DOS R/O          */
#if !defined (__alpha__)
	case 0xf2: /* DOS secondary    */
		return BSD_FS_MSDOS;
#endif
	case 0x07: /* OS/2 HPFS        */
		return BSD_FS_HPFS;
	default:
		break;
	}

	return BSD_FS_OTHER;
}

/**
 * fdisk_bsd_link_partition:
 * @cxt: context
 *
 * Links partition from parent (DOS) to nested BSD partition table.
 *
 * Returns: 0 on success, <0 on error
 */
int fdisk_bsd_link_partition(struct fdisk_context *cxt)
{
	size_t k, i;
	int rc;
	struct dos_partition *p;
	struct bsd_disklabel *d = self_disklabel(cxt);

	if (!cxt->parent || !fdisk_is_label(cxt->parent, DOS)) {
		fdisk_warnx(cxt, _("BSD label is not nested within a DOS partition."));
		return -EINVAL;
	}

	/* ask for DOS partition */
	rc = fdisk_ask_partnum(cxt->parent, &k, FALSE);
	if (rc)
		return rc;
	/* ask for BSD partition */
	rc = fdisk_ask_partnum(cxt, &i, TRUE);
	if (rc)
		return rc;

	if (i >= BSD_MAXPARTITIONS)
		return -EINVAL;

	p = fdisk_dos_get_partition(cxt->parent, k);

	d->d_partitions[i].p_size   = dos_partition_get_size(p);
	d->d_partitions[i].p_offset = dos_partition_get_start(p);
	d->d_partitions[i].p_fstype = bsd_translate_fstype(p->sys_ind);

	if (i >= d->d_npartitions)
		d->d_npartitions = i + 1;

	cxt->label->nparts_cur = d->d_npartitions;
	fdisk_label_set_changed(cxt->label, 1);

	fdisk_info(cxt, _("BSD partition '%c' linked to DOS partition %zu."),
			'a' + (int) i, k + 1);
	return 0;
}


static int bsd_partition_is_used(
		struct fdisk_context *cxt,
		size_t partnum)
{
	struct bsd_disklabel *d = self_disklabel(cxt);

	if (partnum >= BSD_MAXPARTITIONS)
		return 0;

	return d->d_partitions[partnum].p_size ? 1 : 0;
}


static const struct fdisk_label_operations bsd_operations =
{
	.probe		= bsd_probe_label,
	.get_item	= bsd_get_disklabel_item,
	.write		= bsd_write_disklabel,
	.create		= bsd_create_disklabel,

	.del_part	= bsd_delete_part,
	.get_part	= bsd_get_partition,
	.set_part	= bsd_set_partition,
	.add_part	= bsd_add_partition,

	.part_is_used   = bsd_partition_is_used,
};

static const struct fdisk_field bsd_fields[] =
{
	{ FDISK_FIELD_DEVICE,	N_("Slice"),	  1,	0 },
	{ FDISK_FIELD_START,	N_("Start"),	  5,	FDISK_FIELDFL_NUMBER },
	{ FDISK_FIELD_END,	N_("End"),	  5,	FDISK_FIELDFL_NUMBER },
	{ FDISK_FIELD_SECTORS,	N_("Sectors"),    5,	FDISK_FIELDFL_NUMBER },
	{ FDISK_FIELD_CYLINDERS,N_("Cylinders"),  5,	FDISK_FIELDFL_NUMBER },
	{ FDISK_FIELD_SIZE,	N_("Size"),	  5,	FDISK_FIELDFL_NUMBER },
	{ FDISK_FIELD_TYPE,	N_("Type"),	  8,	0 },
	{ FDISK_FIELD_FSIZE,	N_("Fsize"),	  5,	FDISK_FIELDFL_NUMBER },
	{ FDISK_FIELD_BSIZE,	N_("Bsize"),	  5,	FDISK_FIELDFL_NUMBER },
	{ FDISK_FIELD_CPG,	N_("Cpg"),	  5,	FDISK_FIELDFL_NUMBER }
};

/*
 * allocates BSD label driver
 */
struct fdisk_label *fdisk_new_bsd_label(struct fdisk_context *cxt)
{
	struct fdisk_label *lb;
	struct fdisk_bsd_label *bsd;

	assert(cxt);

	bsd = calloc(1, sizeof(*bsd));
	if (!bsd)
		return NULL;

	/* initialize generic part of the driver */
	lb = (struct fdisk_label *) bsd;
	lb->name = "bsd";
	lb->id = FDISK_DISKLABEL_BSD;
	lb->op = &bsd_operations;
	lb->parttypes = bsd_fstypes;
	lb->nparttypes = ARRAY_SIZE(bsd_fstypes) - 1;

	lb->fields = bsd_fields;
	lb->nfields = ARRAY_SIZE(bsd_fields);

	lb->flags |= FDISK_LABEL_FL_INCHARS_PARTNO;
	lb->flags |= FDISK_LABEL_FL_REQUIRE_GEOMETRY;

	return lb;
}
