[BACK]Return to mbr.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / distrib / utils / sysinst

File: [cvs.NetBSD.org] / src / distrib / utils / sysinst / Attic / mbr.c (download)

Revision 1.54, Wed Oct 8 04:25:43 2003 UTC (20 years, 6 months ago) by lukem
Branch: MAIN
Changes since 1.53: +113 -113 lines

Overhaul MBR handling (part 1):

<sys/bootblock.h>:
    *	Added definitions for the Master Boot Record (MBR) used by
	a variety of systems (primarily i386), including the format
	of the BIOS Parameter Block (BPB).
	This information was cribbed from a variety of sources
	including <sys/disklabel_mbr.h> which this is a superset of.

	As part of this, some data structure elements and #defines
	were renamed to be more "namespace friendly" and consistent
	with other bootblocks and MBR documentation.
	Update all uses of the old names to the new names.

<sys/disklabel_mbr.h>:
    *	Deprecated in favor of <sys/bootblock.h> (the latter is more
	"host tool" friendly).

amd64 & i386:
    *	Renamed /usr/mdec/bootxx_dosfs to /usr/mdec/bootxx_msdos, to
	be consistent with the naming convention of the msdosfs tools.

    *	Removed /usr/mdec/bootxx_ufs, as it's equivalent to bootxx_ffsv1
	and it's confusing to have two functionally equivalent bootblocks,
	especially given that "ufs" has multiple meanings (it could be
	a synonym for "ffs", or the group of ffs/lfs/ext2fs file systems).

    *	Rework pbr.S (the first sector of bootxx_*):
	    +	Ensure that BPB (bytes 11..89) and the partition table
		(bytes 446..509) do not contain code.
	    +	Add support for booting from FAT partitions if BOOT_FROM_FAT
		is defined.  (Only set for bootxx_msdos).
	    +	Remove "dummy" partition 3; if people want to installboot(8)
		these to the start of the disk they can use fdisk(8) to
		create a real MBR partition table...
	    +	Compile with TERSE_ERROR so it fits because of the above.
		Whilst this is less user friendly, I feel it's important
		to have a valid partition table and BPB in the MBR/PBR.

    *	Renamed /usr/mdec/biosboot to /usr/mdec/boot, to be consistent
	with other platforms.

    *	Enable SUPPORT_DOSFS in /usr/mdec/boot (stage2), so that
    	we can boot off FAT partitions.

    *	Crank version of /usr/mdec/boot to 3.1, and fix some of the other
	entries in the version file.

installboot(8) (i386):
    *	Read the existing MBR of the filesystem and retain the BIOS
    	Parameter Block (BPB) in bytes 11..89 and the MBR partition
	table in bytes 446..509.  (Previously installboot(8) would
	trash those two sections of the MBR.)

mbrlabel(8):
    *	Use sys/lib/libkern/xlat_mbr_fstype.c instead of homegrown code
	to map the MBR partition type to the NetBSD disklabel type.


Test built "make release" for i386, and new bootblocks verified to work
(even off FAT!).

/*	$NetBSD: mbr.c,v 1.54 2003/10/08 04:25:43 lukem Exp $ */

/*
 * Copyright 1997 Piermont Information Systems Inc.
 * All rights reserved.
 *
 * Written by Philip A. Nelson for Piermont Information Systems Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed for the NetBSD Project by
 *      Piermont Information Systems Inc.
 * 4. The name of Piermont Information Systems Inc. may not be used to endorse
 *    or promote products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY PIERMONT INFORMATION SYSTEMS INC. ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL PIERMONT INFORMATION SYSTEMS INC. BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

/*
 * Following applies to the geometry guessing code
 */

/*
 * Mach Operating System
 * Copyright (c) 1992 Carnegie Mellon University
 * All Rights Reserved.
 *
 * Permission to use, copy, modify and distribute this software and its
 * documentation is hereby granted, provided that both the copyright
 * notice and this permission notice appear in all copies of the
 * software, derivative works or modified versions, and any portions
 * thereof, and that both notices appear in supporting documentation.
 *
 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
 *
 * Carnegie Mellon requests users of this software to return to
 *
 *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
 *  School of Computer Science
 *  Carnegie Mellon University
 *  Pittsburgh PA 15213-3890
 *
 * any improvements or extensions that they make and grant Carnegie Mellon
 * the rights to redistribute these changes.
 */

/* mbr.c -- DOS Master Boot Record editing code */

#include <sys/param.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <util.h>
#include "defs.h"
#include "mbr.h"
#include "md.h"
#include "msg_defs.h"
#include "menu_defs.h"
#include "endian.h"

#define NO_BOOTMENU (-0x100)

#define MAXCYL		1023    /* Possibly 1024 */
#define MAXHEAD		255     /* Possibly 256 */
#define MAXSECTOR	63

struct part_id {
	int id;
	const char *name;
} part_ids[] = {
	{0,			"unused"},
	{MBR_PTYPE_NETBSD,	"NetBSD"},
	{MBR_PTYPE_EXT_LBA,	"Extended partition, LBA"},
	{MBR_PTYPE_386BSD,	"FreeBSD/386BSD"},
	{MBR_PTYPE_OPENBSD,	"OpenBSD"},
	{MBR_PTYPE_LNXEXT2,	"Linux native"},
	{MBR_PTYPE_LNXSWAP,	"Linux swap"},
	{MBR_PTYPE_FAT12,	"DOS FAT12"},
	{MBR_PTYPE_FAT16S,	"DOS FAT16, <32M"},
	{MBR_PTYPE_FAT16B,	"DOS FAT16, >32M"},
	{MBR_PTYPE_FAT16L,	"Windows FAT16, LBA"},
	{MBR_PTYPE_FAT32,	"Windows FAT32"},
	{MBR_PTYPE_FAT32L,	"Windows FAT32, LBA"},
	{MBR_PTYPE_NTFSVOL,	"NTFS volume set"},
	{MBR_PTYPE_NTFS,	"NTFS"},
	{-1,			"Unknown"},
};

static int get_mapping(struct mbr_partition *, int, int *, int *, int *,
			    unsigned long *);
static void convert_mbr_chs(int, int, int, u_int8_t *, u_int8_t *,
				 u_int8_t *, u_int32_t);

/*
 * get C/H/S geometry from user via menu interface and
 * store in globals.
 */
void
set_bios_geom(int cyl, int head, int sec)
{
	char res[80];

	msg_display_add(MSG_setbiosgeom);

	do {
		snprintf(res, 80, "%d", sec);
		msg_prompt_add(MSG_sectors, res, res, 80);
		bsec = atoi(res);
	} while (bsec <= 0 || bsec > 63);

	do {
		snprintf(res, 80, "%d", head);
		msg_prompt_add(MSG_heads, res, res, 80);
		bhead = atoi(res);
	} while (bhead <= 0 || bhead > 256);

	bcyl = dlsize / bsec / bhead;
	if (dlsize != bcyl * bsec * bhead)
		bcyl++;
}

#ifdef notdef
void
disp_cur_geom(void)
{

	msg_display_add(MSG_realgeom, dlcyl, dlhead, dlsec);
	msg_display_add(MSG_biosgeom, bcyl, bhead, bsec);
}
#endif


/*
 * Then,  the partition stuff...
 */

/*
 * If we change the mbr partitioning, the we must remove any references
 * in the netbsd disklabel to the part we changed.
 */
static void
remove_old_partitions(uint start, int size)
{
	partinfo *p;
	uint end;

	/* Allow for size being -ve, get it right for very large partitions */
	end = start + size;
	if (end < start) {
		end = start;
		start = end + size;
	}

	if (end == 0)
		return;

	for (p = oldlabel; p < oldlabel + nelem(oldlabel); p++) {
		if (p->pi_offset >= end || p->pi_offset + p->pi_size <= start)
			continue;
		memset(p, 0, sizeof *p);
	}
}

static int
find_mbr_space(struct mbr_sector *mbrs, uint *start, uint *size, int from, int ignore)
{
	int sz;
	int i;
	uint s, e;

    check_again:
	sz = dlsize - from;
	for (i = 0; i < MBR_PART_COUNT; i++) {
		if (i == ignore)
			continue;
		s = mbrs->mbr_parts[i].mbrp_start;
		e = s + mbrs->mbr_parts[i].mbrp_size;
		if (s <= from && e > from) {
			from = e;
			goto check_again;
		}
		if (s > from && s - from < sz)
			sz = s - from;
	}
	if (sz == 0)
		return -1;
	if (start != NULL)
		*start = from;
	if (size != NULL)
		*size = sz;
	return 0;
}

static struct mbr_partition *
get_mbrp(mbr_info_t **mbrip, int opt)
{
	mbr_info_t *mbri = *mbrip;

	if (opt >= MBR_PART_COUNT)
		for (opt -= MBR_PART_COUNT - 1; opt; opt--)
			mbri = mbri->extended;

	*mbrip = mbri;
	return &mbri->mbr.mbr_parts[opt];
}

static int
set_mbr_type(menudesc *m, void *arg)
{
	mbr_info_t *mbri = arg;
	mbr_info_t *ombri = arg;
	mbr_info_t *ext;
	struct mbr_partition *mbrp;
	char *cp;
	int opt = mbri->opt;
	int type;
	int start, sz;
	int i;
	char numbuf[4];

	mbrp = get_mbrp(&mbri, opt);
	if (opt >= MBR_PART_COUNT)
		opt = 0;

	type = m->cursel;
	if (type == 0)
		return 1;
	type = part_ids[type - 1].id;
	while (type == -1) {
		snprintf(numbuf, sizeof numbuf, "%u", mbrp->mbrp_type);
		msg_prompt_win(MSG_get_ptn_id, -1, 18, 0, 0,
			numbuf, numbuf, sizeof numbuf);
		type = strtoul(numbuf, &cp, 0);
		if (*cp != 0)
			type = -1;
	}

	if (type == mbrp->mbrp_type)
		/* type not changed... */
		return 1;

	mbri->last_mounted[opt < MBR_PART_COUNT ? opt : 0] = NULL;

	if (MBR_IS_EXTENDED(mbrp->mbrp_type)) {
		/* deleting extended partition.... */
		if (mbri->sector || mbri->extended->extended)
			/* We should have stopped this happening... */
			return 0;
		free(mbri->extended);
		mbri->extended = NULL;
	}

	if (type == 0) {
		/* Deleting partition */
		mbrp->mbrp_type = 0;
		remove_old_partitions(mbri->sector + mbrp->mbrp_start,
		    mbrp->mbrp_size);
#ifdef BOOTSEL
		if (ombri->bootsec == mbri->sector + mbrp->mbrp_start)
			ombri->bootsec = 0;
				
		memset(mbri->nametab[opt], 0, sizeof mbri->nametab[opt]);
#endif
		if (mbri->sector == 0) {
			memset(mbrp, 0, sizeof *mbrp);
			return 1;
		}

		/* Merge with previous and next free areas */
		ext = mbri->prev_ext;
		if (ext != NULL && ext->mbr.mbr_parts[0].mbrp_type == 0) {
			mbri = ext;
			ombri->opt--;
		}
		while ((ext = mbri->extended)) {
			if (ext->mbr.mbr_parts[0].mbrp_type != 0)
				break;
			sz = ext->mbr.mbr_parts[0].mbrp_start +
						ext->mbr.mbr_parts[0].mbrp_size;
			/* Increase size of our (empty) partition */
			mbri->mbr.mbr_parts[0].mbrp_size += sz;
			/* Make us describe the next partition */
			mbri->mbr.mbr_parts[1] = ext->mbr.mbr_parts[1];
			/* fix list of extended partitions */
			mbri->extended = ext->extended;
			free(ext);
			if (ext->extended != NULL)
				ext->extended->prev_ext = mbri;
			/* Make previous size cover all our ptn */
			ext = mbri->prev_ext;
			if (ext != NULL)
				ext->mbr.mbr_parts[1].mbrp_size += sz;
		}
		return 1;
	}

	if (mbrp->mbrp_start == 0) {
		/* Find first chunk of space... */
		/* Must be in the main partition */
		if (mbri->sector != 0)
			/* shouldn't be possible to have null start... */
			return 0;
		if (find_mbr_space(&mbri->mbr, &start, &sz, bsec, -1) != 0)
			/* no space */
			return 0;
		mbrp->mbrp_start = start;
		mbrp->mbrp_size = sz;
		/* If there isn't an active partition mark this one active */
		if (!MBR_IS_EXTENDED(type)) {
			for (i = 0; i < MBR_PART_COUNT; i++)
				if (mbri->mbr.mbr_parts[i].mbrp_flag != 0)
					break;
			if (i == MBR_PART_COUNT)
				mbrp->mbrp_flag = MBR_PFLAG_ACTIVE;
		}
	}

	if (MBR_IS_EXTENDED(type)) {
		if (mbri->sector != 0)
			/* Can't set extended partition in an extended one */
			return 0;
		if (mbri->extended)
			/* Can't have two extended partitions */
			return 0;
		/* Create new extended partition */
		ext = calloc(1, sizeof *mbri->extended);
		if (!ext)
			return 0;
		mbri->extended = ext;
		ext->sector = mbrp->mbrp_start;
		ext->mbr.mbr_parts[0].mbrp_start = bsec;
		ext->mbr.mbr_parts[0].mbrp_size = mbrp->mbrp_size - bsec;
	}
	mbrp->mbrp_type = type;

	return 1;
}

static void
set_type_label(menudesc *m, int opt, void *arg)
{

	if (opt == 0) {
		wprintw(m->mw, msg_string(MSG_Dont_change));
		return;
	}
	if (opt == 1) {
		wprintw(m->mw, msg_string(MSG_Delete_partition));
		return;
	}
	if (part_ids[opt - 1].id == -1) {
		wprintw(m->mw, msg_string(MSG_Other_kind));
		return;
	}
	wprintw(m->mw, part_ids[opt - 1].name);
}

static int
edit_mbr_type(menudesc *m, void *arg)
{
	static menu_ent type_opts[1 + nelem(part_ids)];
	static int type_menu = -1;
	int i;

	if (type_menu == -1) {
		for (i = 0; i < nelem(type_opts); i++) {
			type_opts[i].opt_menu = OPT_NOMENU;
			type_opts[i].opt_action = set_mbr_type;
		}
		type_menu = new_menu(NULL, type_opts, nelem(type_opts),
			15, 12, 0, 30,
			MC_SCROLL | MC_NOEXITOPT | MC_NOCLEAR,
			NULL, set_type_label, NULL,
			NULL, NULL);
	}

	if (type_menu != -1)
		process_menu(type_menu, arg);

	return 0;
}

static int
edit_mbr_start(menudesc *m, void *arg)
{
	mbr_info_t *mbri = arg;
	mbr_info_t *ext;
	struct mbr_partition *mbrp;
	int opt = mbri->opt;
	uint start, sz;
	uint new_r, new, limit, dflt_r;
	int delta;
	const char *errmsg;
	char *cp;
	struct {
		uint	start;
		uint	start_r;
		uint	limit;
	} freespace[MBR_PART_COUNT];
	int spaces;
	int i;
	char prompt[MBR_PART_COUNT * 60];
	int len;
	char numbuf[12];

	if (opt >= MBR_PART_COUNT)
		/* should not be able to get here... */
		return 1;

	mbrp = mbri->mbr.mbr_parts + opt;
	/* locate the start of all free areas */
	spaces = 0;
	for (start = bsec, i = 0; i < MBR_PART_COUNT; start += sz, i++) {
		if (find_mbr_space(&mbri->mbr, &start, &sz, start, opt))
			break;
		if (MBR_IS_EXTENDED(mbrp->mbrp_type)) {
			/* Only want the area that contains this partition */
			if (mbrp->mbrp_start < start ||
			    mbrp->mbrp_start >= start + sz)
				continue;
			i = MBR_PART_COUNT - 1;
		}
		freespace[spaces].start = start;
		freespace[spaces].start_r = start / sizemult;
		freespace[spaces].limit = start + sz;
		if (++spaces >= sizeof freespace)
			/* shouldn't happen... */
			break;
	}

	len = 0;
	for (i = 0; i < spaces; i++) {
		len += snprintf(prompt + len, sizeof prompt - len,
		    msg_string(MSG_ptn_starts),
		    freespace[i].start_r,
		    freespace[i].limit / sizemult, multname,
		    freespace[i].limit / sizemult - freespace[i].start_r,
		    multname);
		if (len >= sizeof prompt)
			break;
	}

	dflt_r = mbrp->mbrp_start / sizemult;
	errmsg = "";
	for (;;) {
		snprintf(numbuf, sizeof numbuf, "%d", dflt_r);
		msg_prompt_win(MSG_get_ptn_start, -1, 18, 60, spaces + 3,
			numbuf, numbuf, sizeof numbuf,
			prompt, msg_string(errmsg), multname);
		new_r = strtoul(numbuf, &cp, 0);
		if (*cp != 0) {
			errmsg = MSG_Invalid_numeric;
			continue;
		}
		if (new_r == dflt_r)
			/* Unchanged */
			return 0;
		new = new_r * sizemult;
		for (i = 0; i < spaces; i++) {
			if (new_r == freespace[i].start_r) {
				new = freespace[i].start;
				break;
			}
			if (new >= freespace[i].start &&
			    new < freespace[i].limit)
				break;
		}
		if (i >= spaces) {
			errmsg = MSG_Space_allocated;
			continue;
		}
		limit = freespace[i].limit;
		if (new > mbrp->mbrp_start &&
		    MBR_IS_EXTENDED(mbrp->mbrp_type) &&
		    (mbri->extended->mbr.mbr_parts[0].mbrp_type != 0 ||
			    mbri->extended->mbr.mbr_parts[0].mbrp_size <
						new - mbrp->mbrp_start)) {
			errmsg = MSG_Space_allocated;
			continue;
		}
		break;
	}

	if (new < mbrp->mbrp_start + mbrp->mbrp_size &&
	    limit > mbrp->mbrp_start)
		/* Keep end of partition in the same place */
		limit = mbrp->mbrp_start + mbrp->mbrp_size;

	delta = new - mbrp->mbrp_start;
	if (MBR_IS_EXTENDED(mbrp->mbrp_type)) {
		ext = mbri->extended;
		if (ext->mbr.mbr_parts[0].mbrp_type != 0) {
			/* allocate an extended ptn for the free item */
			ext = calloc(1, sizeof *ext);
			if (!ext)
				return 0;
			ext->sector = mbrp->mbrp_start;
			ext->extended = mbri->extended;
			mbri->extended->prev_ext = ext;
			mbri->extended = ext;
			ext->mbr.mbr_parts[0].mbrp_start = bsec;
			ext->mbr.mbr_parts[0].mbrp_size = -bsec;
			ext->mbr.mbr_parts[1].mbrp_type = MBR_PTYPE_EXT;
			ext->mbr.mbr_parts[1].mbrp_start = 0;
			ext->mbr.mbr_parts[1].mbrp_size =
				ext->extended->mbr.mbr_parts[0].mbrp_start +
				ext->extended->mbr.mbr_parts[0].mbrp_size;
		}
		/* adjust size of first free item */
		ext->mbr.mbr_parts[0].mbrp_size -= delta;
		ext->sector += delta;
		/* and the link of all extended partitions */
		do
			if (ext->extended)
				ext->mbr.mbr_parts[1].mbrp_start -= delta;
		while ((ext = ext->extended));
	}
	remove_old_partitions(mbri->sector + mbrp->mbrp_start, delta);

	/* finally set partition base and size */
	mbrp->mbrp_start = new;
	mbrp->mbrp_size = limit - new;
	mbri->last_mounted[opt] = NULL;

	return 0;
}

static int
edit_mbr_size(menudesc *m, void *arg)
{
	mbr_info_t *mbri = arg;
	mbr_info_t *ombri = arg;
	mbr_info_t *ext;
	struct mbr_partition *mbrp;
	int opt = mbri->opt;
	uint start, max, max_r, dflt, dflt_r, new;
	uint freespace;
	int delta;
	char numbuf[12];
	char *cp;
	const char *errmsg;

	mbrp = get_mbrp(&mbri, opt);
	dflt = mbrp->mbrp_size;
	if (opt < MBR_PART_COUNT) {
		max = 0;
		find_mbr_space(&mbri->mbr, &start, &max, mbrp->mbrp_start, opt);
		if (start != mbrp->mbrp_start)
			return 0;
		if (dflt == 0)
			dflt = max;
	} else {
		ext = mbri->extended;
		max = dflt;
		if (ext != NULL && ext->mbr.mbr_parts[0].mbrp_type == 0) {
			/* Easier to merge now and split later... */
			if (ext->extended)
				ext->extended->prev_ext = mbri;
			mbri->extended = ext->extended;
			if (mbri->prev_ext)
				mbri->prev_ext->mbr.mbr_parts[1].mbrp_size
					+= mbri->mbr.mbr_parts[1].mbrp_size;
			mbrp->mbrp_size += mbri->mbr.mbr_parts[1].mbrp_size;
			max += mbri->mbr.mbr_parts[1].mbrp_size;
			mbri->mbr.mbr_parts[1] = ext->mbr.mbr_parts[1];
			free(ext);
		}
	}

	start = mbri->sector + mbrp->mbrp_start;
	dflt_r = (start + dflt) / sizemult - start / sizemult;
	if (max == dflt)
		max_r = dflt_r;
	else
		max_r = max / sizemult;
	for (errmsg = "";;) {
		snprintf(numbuf, sizeof numbuf, "%d", dflt_r);
		msg_prompt_win(MSG_get_ptn_size, -1, 18, 0, 0,
			numbuf, numbuf, sizeof numbuf,
			msg_string(errmsg), max_r, multname);
		new = strtoul(numbuf, &cp, 0);
		if (*cp != 0) {
			errmsg = MSG_Invalid_numeric;
			continue;
		}
		if (new == 0 || new == max_r)
			new = max;
		else {
			if (new == dflt_r)
				new = dflt;
			else {
				/* Round end to cylinder boundary */
				if (sizemult != 1) {
					new = start + new * sizemult;
					new = ROUNDUP(new, bcylsize);
					new -= start;
				}
			}
		}
		if (new > max) {
			errmsg = MSG_Too_large;
			continue;
		}
		if (!MBR_IS_EXTENDED(mbrp->mbrp_type) || new == dflt)
			break;
		/* Must keep extended list aligned */
		for (ext = mbri->extended; ext->extended; ext = ext->extended)
			continue;
		if ((new < dflt && (ext->mbr.mbr_parts[0].mbrp_type != 0
			    || (mbrp->mbrp_start + new < ext->sector + bsec
				&& mbrp->mbrp_start + new != ext->sector)))
		    || (new > dflt && ext->mbr.mbr_parts[0].mbrp_type != 0
							&& new < dflt + bsec)) {
			errmsg = MSG_Space_allocated;
			continue;
		}
		delta = new - dflt;
		if (ext->mbr.mbr_parts[0].mbrp_type == 0) {
			/* adjust size of last item (free space) */
			if (mbrp->mbrp_start + new == ext->sector) {
				/* kill last extended ptn */
				ext = ext->prev_ext;
				free(ext->extended);
				ext->extended = NULL;
				memset(&ext->mbr.mbr_parts[1], 0,
					sizeof ext->mbr.mbr_parts[1]);
				break;
			}
			ext->mbr.mbr_parts[0].mbrp_size += delta;
			ext = ext->prev_ext;
			if (ext != NULL)
				ext->mbr.mbr_parts[1].mbrp_size += delta;
			break;
		}
		/* Joy of joys, we must allocate another extended ptn */
		mbri = ext;
		ext = calloc(1, sizeof *ext);
		if (!ext)
			return 0;
		mbri->extended = ext;
		ext->prev_ext = mbri;
		ext->mbr.mbr_parts[0].mbrp_start = bsec;
		ext->mbr.mbr_parts[0].mbrp_size = delta - bsec;
		ext->sector = mbri->sector + mbri->mbr.mbr_parts[0].mbrp_start
					   + mbri->mbr.mbr_parts[0].mbrp_size;
		mbri->mbr.mbr_parts[1].mbrp_start = ext->sector - ombri->sector;
		mbri->mbr.mbr_parts[1].mbrp_type = MBR_PTYPE_EXT;
		mbri->mbr.mbr_parts[1].mbrp_size = delta;
		break;
	}

	if (opt >= MBR_PART_COUNT && max - new <= bsec)
		/* round up if not enough space for a header */
		new = max;

	if (new != mbrp->mbrp_size) {
		mbri->last_mounted[opt < MBR_PART_COUNT ? opt : 0] = NULL;
		remove_old_partitions(mbri->sector + mbrp->mbrp_start +
					mbrp->mbrp_size, new - mbrp->mbrp_size);
	}

	mbrp->mbrp_size = new;
	if (opt < MBR_PART_COUNT || new == max)
		return 0;

	/* Need to allocate an extra item for the free space */
	ext = calloc(1, sizeof *ext);
	if (!ext) {
		mbrp->mbrp_size = max;
		return 0;
	}
	/* Link into our extended chain */
	ext->extended = mbri->extended;
	mbri->extended = ext;
	ext->prev_ext = mbri;
	if (ext->extended != NULL)
		ext->extended->prev_ext = ext;
	ext->mbr.mbr_parts[1] = mbri->mbr.mbr_parts[1];
	freespace = max - new;
	if (mbri->prev_ext != NULL)
		mbri->prev_ext->mbr.mbr_parts[1].mbrp_size -= freespace;

	ext->mbr.mbr_parts[0].mbrp_start = bsec;
	ext->mbr.mbr_parts[0].mbrp_size = freespace - bsec;

	ext->sector = mbri->sector + mbri->mbr.mbr_parts[0].mbrp_start
				   + mbri->mbr.mbr_parts[0].mbrp_size;
	mbri->mbr.mbr_parts[1].mbrp_start = ext->sector - ombri->sector;
	mbri->mbr.mbr_parts[1].mbrp_type = MBR_PTYPE_EXT;
	mbri->mbr.mbr_parts[1].mbrp_size = freespace;

	return 0;
}

static int
edit_mbr_active(menudesc *m, void *arg)
{
	mbr_info_t *mbri = arg;
	int i;
	uint8_t *fl;

	if (mbri->opt >= MBR_PART_COUNT)
		/* sanity */
		return 0;

	/* Invert active flag */
	fl = &mbri->mbr.mbr_parts[mbri->opt].mbrp_flag;
	if (*fl == MBR_PFLAG_ACTIVE) {
		*fl = 0;
		return 0;
	}
		
	/* Ensure there is at most one active partition */
	for (i = 0; i < MBR_PART_COUNT; i++)
		mbri->mbr.mbr_parts[i].mbrp_flag = 0;
	*fl = MBR_PFLAG_ACTIVE;

	return 0;
}

static int
edit_mbr_install(menudesc *m, void *arg)
{
	mbr_info_t *mbri = arg;
	mbr_info_t *ombri = arg;
	struct mbr_partition *mbrp;
	int opt = mbri->opt;
	uint start;

	mbrp = get_mbrp(&mbri, opt);
	if (opt >= MBR_PART_COUNT)
		opt = 0;

	start = mbri->sector + mbrp->mbrp_start;
	if (start == ombri->install)
		ombri->install = 0;
	else
		ombri->install = start;
	return 0;
}

#ifdef BOOTSEL
static int
edit_mbr_bootmenu(menudesc *m, void *arg)
{
	mbr_info_t *mbri = arg;
	mbr_info_t *ombri = arg;
	struct mbr_partition *mbrp;
	int opt = mbri->opt;

	mbrp = get_mbrp(&mbri, opt);
	if (opt >= MBR_PART_COUNT)
		opt = 0;

	msg_prompt_win(/* XXX */ "bootmenu", -1, 18, 0, 0,
		mbri->nametab[opt],
		mbri->nametab[opt], sizeof mbri->nametab[opt]);
	if (mbri->nametab[opt][0] == ' ')
		mbri->nametab[opt][0] = 0;
	if (mbri->nametab[opt][0] == 0) {
		if (ombri->bootsec == mbri->sector + mbrp->mbrp_start)
			ombri->bootsec = 0;
		return 0;
	}
	return 0;
}

static int
edit_mbr_bootdefault(menudesc *m, void *arg)
{
	mbr_info_t *mbri = arg;
	mbr_info_t *ombri = arg;
	struct mbr_partition *mbrp;

	mbrp = get_mbrp(&mbri, mbri->opt);

	ombri->bootsec = mbri->sector + mbrp->mbrp_start;
	return 0;
}
#endif

static void set_ptn_label(menudesc *m, int line, void *arg);
static void set_ptn_header(menudesc *m, void *arg);

static int
edit_mbr_entry(menudesc *m, void *arg)
{
	mbr_info_t *mbri = arg;
	static int ptn_menu = -1;

	static menu_ent ptn_opts[] = {
#define PTN_OPT_TYPE		0
		{NULL, OPT_NOMENU, 0, edit_mbr_type},
#define PTN_OPT_START		1
		{NULL, OPT_NOMENU, 0, edit_mbr_start},
#define PTN_OPT_SIZE		2
		{NULL, OPT_NOMENU, 0, edit_mbr_size},
#define PTN_OPT_END		3
		{NULL, OPT_NOMENU, OPT_IGNORE, NULL},	/* display end */
#define PTN_OPT_ACTIVE		4
		{NULL, OPT_NOMENU, 0, edit_mbr_active},
#define PTN_OPT_INSTALL		5
		{NULL, OPT_NOMENU, 0, edit_mbr_install},
#ifdef BOOTSEL
#define PTN_OPT_BOOTMENU	6
		{NULL, OPT_NOMENU, 0, edit_mbr_bootmenu},
#define PTN_OPT_BOOTDEFAULT	7
		{NULL, OPT_NOMENU, 0, edit_mbr_bootdefault},
#endif
		{MSG_askunits, MENU_sizechoice, OPT_SUB, NULL},
	};

	if (ptn_menu == -1)
		ptn_menu = new_menu(NULL, ptn_opts, nelem(ptn_opts),
			15, 6, 0, 50,
			MC_SCROLL | MC_NOCLEAR,
			set_ptn_header, set_ptn_label, NULL,
			NULL, MSG_Partition_OK);
	if (ptn_menu == -1)
		return 1;

	mbri->opt = m->cursel;
	process_menu(ptn_menu, mbri);
	return 0;
}

static void
set_ptn_label(menudesc *m, int line, void *arg)
{
	mbr_info_t *mbri = arg;
	mbr_info_t *ombri = arg;
	struct mbr_partition *mbrp;
	int opt;
	static const char *yes, *no;

	if (yes == NULL) {
		yes = msg_string(MSG_Yes);
		no = msg_string(MSG_No);
	}

	opt = mbri->opt;
	mbrp = get_mbrp(&mbri, opt);
	if (opt >= MBR_PART_COUNT)
		opt = 0;

	switch (line) {
	case PTN_OPT_TYPE:
		wprintw(m->mw, msg_string(MSG_ptn_type),
			get_partname(mbrp->mbrp_type));
		break;
	case PTN_OPT_START:
		wprintw(m->mw, msg_string(MSG_ptn_start),
		    (mbri->sector + mbrp->mbrp_start) / sizemult, multname);
		break;
	case PTN_OPT_SIZE:
		wprintw(m->mw, msg_string(MSG_ptn_size),
		    (mbri->sector + mbrp->mbrp_start + mbrp->mbrp_size) /
			    sizemult -
		    (mbri->sector + mbrp->mbrp_start) / sizemult, multname);
		break;
	case PTN_OPT_END:
		wprintw(m->mw, msg_string(MSG_ptn_end),
		    (mbri->sector + mbrp->mbrp_start + mbrp->mbrp_size) /
			    sizemult, multname);
		break;
	case PTN_OPT_ACTIVE:
		wprintw(m->mw, msg_string(MSG_ptn_active),
		    mbrp->mbrp_flag == MBR_PFLAG_ACTIVE ? yes : no);
		break;
	case PTN_OPT_INSTALL:
		wprintw(m->mw, msg_string(MSG_ptn_install),
		    mbri->sector + mbrp->mbrp_start == ombri->install &&
			mbrp->mbrp_type == MBR_PTYPE_NETBSD ? yes : no);
		break;
#ifdef BOOTSEL
	case PTN_OPT_BOOTMENU:
		wprintw(m->mw, msg_string(MSG_bootmenu), mbri->nametab[opt]);
		break;
	case PTN_OPT_BOOTDEFAULT:
		wprintw(m->mw, msg_string(MSG_boot_dflt),
		    ombri->bootsec == mbri->sector + mbrp->mbrp_start ? yes
								      : no);
		break;
#endif
	}

}

static void
set_ptn_header(menudesc *m, void *arg)
{
	mbr_info_t *mbri = arg;
	struct mbr_partition *mbrp;
	int opt = mbri->opt;
	int typ;

	mbrp = get_mbrp(&mbri, opt);
	if (opt >= MBR_PART_COUNT)
		opt = 0;
	typ = mbrp->mbrp_type;

#define DISABLE(opt,cond) \
	if (cond) \
		m->opts[opt].opt_flags |= OPT_IGNORE; \
	else \
		m->opts[opt].opt_flags &= ~OPT_IGNORE;

	/* Can't change type of the extended partition unless it is empty */
	DISABLE(PTN_OPT_TYPE, MBR_IS_EXTENDED(typ) &&
	    (mbri->extended->mbr.mbr_parts[0].mbrp_type != 0 ||
					    mbri->extended->extended != NULL));

	/* It is unnecessary to be able to change the base of an extended ptn */
	DISABLE(PTN_OPT_START, mbri->sector || typ == 0);

	/* or the size of a free area */
	DISABLE(PTN_OPT_SIZE, typ == 0);

	/* Only 'normal' partitions can be 'Active' */
	DISABLE(PTN_OPT_ACTIVE, mbri->sector != 0 || MBR_IS_EXTENDED(typ) || typ == 0);

	/* Can only install into NetBSD partition */
	DISABLE(PTN_OPT_INSTALL, typ != MBR_PTYPE_NETBSD);

#ifdef BOOTSEL
	/* The extended partition isn't bootable */
	DISABLE(PTN_OPT_BOOTMENU, MBR_IS_EXTENDED(typ) || typ == 0);

	if (typ == 0)
		mbri->nametab[opt][0] = 0;

	/* Only partitions with bootmenu names can be made the default */
	DISABLE(PTN_OPT_BOOTDEFAULT, mbri->nametab[opt][0] == 0);
#endif
#undef DISABLE
}

static void
set_mbr_label(menudesc *m, int opt, void *arg)
{
	mbr_info_t *mbri = arg;
	mbr_info_t *ombri = arg;
	struct mbr_partition *mbrp;
	uint rstart, rend;
	const char *name, *cp, *mounted;
	int len;

	mbrp = get_mbrp(&mbri, opt);
	if (opt >= MBR_PART_COUNT)
		opt = 0;

	if (mbrp->mbrp_type == 0 && mbri->sector == 0) {
		len = snprintf(0, 0, msg_string(MSG_part_row_used), 0, 0, 0);
		wprintw(m->mw, "%*s", len, "");
	} else {
		rstart = mbri->sector + mbrp->mbrp_start;
		rend = (rstart + mbrp->mbrp_size) / sizemult;
		rstart = rstart / sizemult;
		wprintw(m->mw, msg_string(MSG_part_row_used),
		    rstart, rend - rstart,
		    mbrp->mbrp_flag == MBR_PFLAG_ACTIVE ? 'a' : ' ',
#ifdef BOOTSEL
		    ombri->bootsec == mbri->sector + mbrp->mbrp_start ? 'd' :
#endif
			' ',
		    mbri->sector + mbrp->mbrp_start == ombri->install &&
			mbrp->mbrp_type == MBR_PTYPE_NETBSD ? 'I' : ' ');
	}
	name = get_partname(mbrp->mbrp_type);
	mounted = mbri->last_mounted[opt];
	len = strlen(name);
	cp = strchr(name, ',');
	if (cp != NULL)
		len = cp - name;
	if (mounted && *mounted != 0) {
		wprintw(m->mw, " %*s (%s)", len, name, mounted);
	} else
		wprintw(m->mw, " %.*s", len, name);
#ifdef BOOTSEL
	if (mbri->nametab[opt][0] != 0) {
		int x, y;
		if (opt >= MBR_PART_COUNT)
			opt = 0;
		getyx(m->mw, y, x);
		if (x > 52) {
			x = 52;
			wmove(m->mw, y, x);
		}
		wprintw(m->mw, "%*s %s", 53 - x, "", mbri->nametab[opt]);
	}
#endif
}

static void
set_mbr_header(menudesc *m, void *arg)
{
	mbr_info_t *mbri = arg;
	static menu_ent *opts;
	static int num_opts;
	mbr_info_t *ext;
	menu_ent *op;
	int i;
	int left;

	msg_display(MSG_editparttable);

	msg_table_add(MSG_part_header, dlsize/sizemult, multname, multname,
	    multname, multname);

	if (num_opts == 0) {
		num_opts = 6;
		opts = malloc(6 * sizeof *opts);
		if (opts == NULL) {
			m->numopts = 0;
			return;
		}
	}

	/* First four items are the main partitions */
	for (op = opts, i = 0; i < MBR_PART_COUNT; op++, i++) {
		op->opt_name = NULL;
		op->opt_menu = OPT_NOMENU;
		op->opt_flags = OPT_SUB;
		op->opt_action = edit_mbr_entry;
	}
	left = num_opts - MBR_PART_COUNT;

	/* Followed by the extended partitions */
	for (ext = mbri->extended; ext; left--, op++, ext = ext->extended) {
		if (left <= 1) {
			menu_ent *new = realloc(opts,
						(num_opts + 4) * sizeof *opts);
			if (new == NULL)
				break;
			num_opts += 4;
			left += 4;
			op = new + (op - opts);
			opts = new;
		}
		op->opt_name = NULL;
		op->opt_menu = OPT_NOMENU;
		op->opt_flags = 0;
		op->opt_action = edit_mbr_entry;
	}

	/* and unit changer */
	op->opt_name = MSG_askunits;
	op->opt_menu = MENU_sizechoice;
	op->opt_flags = OPT_SUB;
	op->opt_action = NULL;
	op++;

	m->opts = opts;
	m->numopts = op - opts;
}

/*
 * Let user change incore Master Boot Record partitions via menu.
 */
int
edit_mbr(mbr_info_t *mbri)
{
	struct mbr_sector *mbrs = &mbri->mbr;
	mbr_info_t *ext;
	struct mbr_partition *part;
	int i, j;
	int usefull;
	int mbr_menu;
	int activepart;
	int numbsd;
	uint bsdstart, bsdsize;
	uint start;

	/* Ask full/part */

	part = &mbrs->mbr_parts[0];
	msg_display(MSG_fullpart, diskdev);
	process_menu(MENU_fullpart, &usefull);

	/* DOS fdisk label checking and value setting. */
	if (usefull) {
		/* Count nonempty, non-BSD partitions. */
		numbsd = 0;
		for (i = 0; i < MBR_PART_COUNT; i++) {
			j = part[i].mbrp_type;
			if (j == 0)
				continue;
			numbsd++;
			if (j != MBR_PTYPE_NETBSD)
				numbsd++;
		}

		/* Ask if we really want to blow away non-NetBSD stuff */
		if (numbsd > 1) {
			msg_display(MSG_ovrwrite);
			process_menu(MENU_noyes, NULL);
			if (!yesno) {
				if (logging)
					(void)fprintf(logfp, "User answered no to destroy other data, aborting.\n");
				return 0;
			}
		}

		/* Set the partition information for full disk usage. */
		while ((ext = mbri->extended)) {
			mbri->extended = ext->extended;
			free(ext);
		}
		memset(part, 0, MBR_PART_COUNT * sizeof *part);
#ifdef BOOTSEL
		memset(&mbri->nametab, 0, sizeof mbri->nametab);
#endif
		part[0].mbrp_type = MBR_PTYPE_NETBSD;
		part[0].mbrp_size = dlsize - bsec;
		part[0].mbrp_start = bsec;
		part[0].mbrp_flag = MBR_PFLAG_ACTIVE;

		ptstart = bsec;
		ptsize = dlsize - bsec;
		return 1;
	}

	mbr_menu = new_menu(NULL, NULL, 16, 0, -1, 15, 70,
			MC_NOBOX | MC_SCROLL | MC_NOCLEAR,
			set_mbr_header, set_mbr_label, NULL,
			NULL, MSG_Partition_table_ok);
	if (mbr_menu == -1)
		return 0;

	/* Ask for sizes, which partitions, ... */
	sizemult = MEG / sectorsize;
	ask_sizemult(bcylsize);

	for (;;) {
		ptstart = 0;
		ptsize = 0;
		process_menu(mbr_menu, mbri);

		activepart = 0;
		bsdstart = 0;
		bsdsize = 0;
		for (ext = mbri; ext; ext = ext->extended) {
			part = ext->mbr.mbr_parts;
			for (i = 0; i < MBR_PART_COUNT; part++, i++) {
				if (part->mbrp_flag != 0)
					activepart = 1;
				if (part->mbrp_type != MBR_PTYPE_NETBSD)
					continue;
				start = ext->sector + part->mbrp_start;
				if (start == mbri->install) {
					ptstart = mbri->install;
					ptsize = part->mbrp_size;
				}
				if (bsdstart != 0)
					bsdstart = ~0;
				else {
					bsdstart = start;
					bsdsize = part->mbrp_size;
				}
			}
		}

		/* Install in only netbsd partition if none tagged */
		if (ptstart == 0 && bsdstart != ~0) {
			ptstart = bsdstart;
			ptsize = bsdsize;
		}

		if (ptstart == 0) {
			if (bsdstart == 0)
				msg_display(MSG_nobsdpart);
			else
				msg_display(MSG_multbsdpart, 0);
			msg_display_add(MSG_reeditpart, 0);
			process_menu(MENU_yesno, NULL);
			if (!yesno)
				return 0;
			continue;
		}

		if (activepart == 0) {
			msg_display(MSG_noactivepart);
			process_menu(MENU_yesno, NULL);
			if (yesno)
				continue;
		}
		break;
	}

	free_menu(mbr_menu);

	return 1;
}

const char *
get_partname(int typ)
{
	int j;
	static char unknown[32];

	for (j = 0; part_ids[j].id != -1; j++)
		if (part_ids[j].id == typ)
			return part_ids[j].name;

	snprintf(unknown, sizeof unknown, "Unknown (%d)", typ);
	return unknown;
}

int
read_mbr(const char *disk, mbr_info_t *mbri)
{
	struct mbr_partition *mbrp;
	struct mbr_sector *mbrs = &mbri->mbr;
	mbr_info_t *ext = NULL;
	char diskpath[MAXPATHLEN];
	int fd, i;
	uint32_t ext_base = 0, next_ext = 0, ext_size = 0;
	int rval = -1;
#ifdef BOOTSEL
	mbr_info_t *ombri = mbri;
	int bootkey;
#endif

	memset(mbri, 0, sizeof *mbri);

	/* Open the disk. */
	fd = opendisk(disk, O_RDONLY, diskpath, sizeof(diskpath), 0);
	if (fd < 0)
		goto bad_mbr;

	for (;;) {
		if (pread(fd, mbrs, sizeof *mbrs,
		    (ext_base + next_ext) * (off_t)MBR_SECSIZE) < sizeof *mbrs)
			break;

		if (!valid_mbr(mbrs))
			break;

		mbrp = &mbrs->mbr_parts[0];
		if (ext_base != 0) {
			/* sanity check extended chain */
			if (MBR_IS_EXTENDED(mbrp[0].mbrp_type))
				break;
			if (mbrp[1].mbrp_type != 0 &&
			    !MBR_IS_EXTENDED(mbrp[1].mbrp_type))
				break;
			if (mbrp[2].mbrp_type != 0 || mbrp[3].mbrp_type != 0)
				break;
			mbri->extended = ext;
			ext->prev_ext = ext_base != 0 ? mbri : NULL;
			ext->extended = NULL;
			mbri = ext;
			ext = NULL;
		}
#if BOOTSEL
		else {
			if (mbrs->mbr_bootsel.mbrbs_magic == htole16(MBR_MAGIC))
				bootkey = mbrs->mbr_bootsel.mbrbs_defkey;
			else
				bootkey = 0;
			bootkey -= SCAN_1;
		}
		if (mbrs->mbr_bootsel.mbrbs_magic == htole16(MBR_MAGIC))
			memcpy(mbri->nametab, mbrs->mbr_bootsel.mbrbs_nametab,
				sizeof mbri->nametab);
#endif
		mbri->sector = next_ext + ext_base;
		next_ext = 0;
		rval = 0;
		for (i = 0; i < MBR_PART_COUNT; mbrp++, i++) {
			if (mbrp->mbrp_type == 0) {
				/* type is unused, discard scum */
				memset(mbrp, 0, sizeof *mbrp);
				continue;
			}
			mbrp->mbrp_start = le32toh(mbrp->mbrp_start);
			mbrp->mbrp_size = le32toh(mbrp->mbrp_size);
			if (MBR_IS_EXTENDED(mbrp->mbrp_type)) {
				next_ext = mbrp->mbrp_start;
				if (ext_base == 0)
					ext_size = mbrp->mbrp_size;
			} else {
				mbri->last_mounted[i] = strdup(get_last_mounted(
					fd, mbri->sector + mbrp->mbrp_start));
#if BOOTSEL
				if (ombri->install == 0 &&
				    strcmp(mbri->last_mounted[i], "/") == 0)
					ombri->install = mbri->sector +
							mbrp->mbrp_start;
#endif
			}
#if BOOTSEL
			if (mbri->nametab[i][0] != 0 && bootkey-- == 0)
				ombri->bootsec = mbri->sector +
							mbrp->mbrp_start;
#endif
		}

		if (ext_base != 0) {
			/* Is there a gap before the next partition? */
			unsigned int limit = next_ext;
			unsigned int base;
			if (limit == 0)
				limit = ext_size;
			mbrp -= MBR_PART_COUNT;
			base =mbri->sector + mbrp->mbrp_start + mbrp->mbrp_size;
			if (mbrp->mbrp_type != 0 && ext_base + limit != base) {
				/* Mock up an extry for the space */
				ext = calloc(1, sizeof *ext);
				if (!ext)
					break;
				ext->sector = base;
				ext->mbr.mbr_magic = htole16(MBR_MAGIC);
				ext->mbr.mbr_parts[1] = mbrp[1];
				mbrp[1].mbrp_type = MBR_PTYPE_EXT;
				mbrp[1].mbrp_start = base - ext_base;
				mbrp[1].mbrp_size = limit - mbrp[1].mbrp_start;
				mbri->extended = ext;
				ext->prev_ext = mbri;
				ext->extended = NULL;
				mbri = ext;
				ext = NULL;
			}
		}

		if (next_ext == 0 || ext_base + next_ext <= mbri->sector)
			break;
		if (ext_base == 0) {
			ext_base = next_ext;
			next_ext = 0;
		}
		ext = malloc(sizeof *ext);
		if (!ext)
			break;
		mbrs = &ext->mbr;
	}

    bad_mbr:
	free(ext);
	if (fd >= 0)
		close(fd);
	if (rval == -1) {
		memset(&mbrs->mbr_parts, 0, sizeof mbrs->mbr_parts);
		mbrs->mbr_magic = htole16(MBR_MAGIC);
	}
	return rval;
}

int
write_mbr(const char *disk, mbr_info_t *mbri, int convert)
{
	char diskpath[MAXPATHLEN];
	int fd, i, ret = 0;
	struct mbr_partition *mbrp;
	u_int32_t pstart, psize;
	struct mbr_sector *mbrs;
	mbr_info_t *ext;
#ifdef BOOTSEL
	int netbsd_bootcode;
	int8_t key = SCAN_1;

	if (mbri->mbr.mbr_bootsel.mbrbs_magic == htole16(MBR_MAGIC)) {
		netbsd_bootcode = 1;
		mbri->mbr.mbr_bootsel.mbrbs_defkey = SCAN_ENTER;
	} else
		netbsd_bootcode = 0;
#endif

	/* Open the disk. */
	fd = opendisk(disk, O_WRONLY, diskpath, sizeof(diskpath), 0);
	if (fd < 0)
		return -1;

	for (ext = mbri; ext != NULL; ext = ext->extended) {
		mbrs = &ext->mbr;
#ifdef BOOTSEL
		if (netbsd_bootcode) {
			mbrs->mbr_bootsel.mbrbs_magic = htole16(MBR_MAGIC);
			memcpy(&mbrs->mbr_bootsel.mbrbs_nametab, &ext->nametab,
			    sizeof mbrs->mbr_bootsel.mbrbs_nametab);
		}
#endif
		mbrp = &mbrs->mbr_parts[0];
		for (i = 0; i < MBR_PART_COUNT; i++) {
			if (mbrp[i].mbrp_start == 0 && mbrp[i].mbrp_size == 0) {
				mbrp[i].mbrp_scyl = 0;
				mbrp[i].mbrp_shd = 0;
				mbrp[i].mbrp_ssect = 0;
				mbrp[i].mbrp_ecyl = 0;
				mbrp[i].mbrp_ehd = 0;
				mbrp[i].mbrp_esect = 0;
				continue;
			}
			pstart = mbrp[i].mbrp_start;
			psize = mbrp[i].mbrp_size;
			mbrp[i].mbrp_start = htole32(pstart);
			mbrp[i].mbrp_size = htole32(psize);
			if (convert) {
				convert_mbr_chs(bcyl, bhead, bsec,
				    &mbrp[i].mbrp_scyl, &mbrp[i].mbrp_shd,
				    &mbrp[i].mbrp_ssect, pstart);
				convert_mbr_chs(bcyl, bhead, bsec,
				    &mbrp[i].mbrp_ecyl, &mbrp[i].mbrp_ehd,
				    &mbrp[i].mbrp_esect, pstart + psize - 1);
			}
#ifdef BOOTSEL
			if (netbsd_bootcode && ext->nametab[i][0] != 0) {
				if (ext->sector + pstart == mbri->bootsec)
					mbri->mbr.mbr_bootsel.mbrbs_defkey = key;
				key++;
			}
#endif
		}

		mbrs->mbr_magic = htole16(MBR_MAGIC);
		/*
		 * Sector zero is written outside the loop after we have
		 * set mbrbs_defkey.
		 */
		if (ext->sector != 0 && pwrite(fd, mbrs, sizeof *mbrs,
		    ext->sector * (off_t)MBR_SECSIZE) < 0)
			ret = -1;
	}

	if (pwrite(fd, &mbri->mbr, sizeof mbri->mbr, 0) < 0)
		ret = -1;

	(void)close(fd);
	return ret;
}

int
valid_mbr(struct mbr_sector *mbrs)
{

	return (le16toh(mbrs->mbr_magic) == MBR_MAGIC);
}

static void
convert_mbr_chs(int cyl, int head, int sec,
		u_int8_t *cylp, u_int8_t *headp, u_int8_t *secp,
		u_int32_t relsecs)
{
	unsigned int tcyl, temp, thead, tsec;

	temp = head * sec;
	tcyl = relsecs / temp;
	relsecs -= tcyl * temp;

	thead = relsecs / sec;
	tsec = relsecs - thead * sec + 1;

	if (tcyl > MAXCYL)
		tcyl = MAXCYL;

	*cylp = MBR_PUT_LSCYL(tcyl);
	*headp = thead;
	*secp = MBR_PUT_MSCYLANDSEC(tcyl, tsec);
}

/*
 * This function is ONLY to be used as a last resort to provide a
 * hint for the user. Ports should provide a more reliable way
 * of getting the BIOS geometry. The i386 code, for example,
 * uses the BIOS geometry as passed on from the bootblocks,
 * and only uses this as a hint to the user when that information
 * is not present, or a match could not be made with a NetBSD
 * device.
 */

int
guess_biosgeom_from_mbr(mbr_info_t *mbri, int *cyl, int *head, int *sec)
{
	struct mbr_sector *mbrs = &mbri->mbr;
	struct mbr_partition *parts = &mbrs->mbr_parts[0];
	int xcylinders, xheads, xsectors, i, j;
	int c1, h1, s1, c2, h2, s2;
	unsigned long a1, a2;
	uint64_t num, denom;

	/*
	 * The physical parameters may be invalid as bios geometry.
	 * If we cannot determine the actual bios geometry, we are
	 * better off picking a likely 'faked' geometry than leaving
	 * the invalid physical one.
	 */

	xcylinders = dlcyl;
	xheads = dlhead;
	xsectors = dlsec;
	if (xcylinders > MAXCYL || xheads > MAXHEAD || xsectors > MAXSECTOR) {
		xsectors = MAXSECTOR;
		xheads = MAXHEAD;
		xcylinders = dlsize / (MAXSECTOR * MAXHEAD);
		if (xcylinders > MAXCYL)
			xcylinders = MAXCYL;
	}
	*cyl = xcylinders;
	*head = xheads;
	*sec = xsectors;

	xheads = -1;

	/* Try to deduce the number of heads from two different mappings. */
	for (i = 0; i < MBR_PART_COUNT * 2 - 1; i++) {
		if (get_mapping(parts, i, &c1, &h1, &s1, &a1) < 0)
			continue;
		for (j = i + 1; j < MBR_PART_COUNT * 2; j++) {
			if (get_mapping(parts, j, &c2, &h2, &s2, &a2) < 0)
				continue;
			a1 -= s1;
			a2 -= s2;
			num = (uint64_t)h1 * a2 - (quad_t)h2 * a1;
			denom = (uint64_t)c2 * a1 - (quad_t)c1 * a2;
			if (denom != 0 && num % denom == 0) {
				xheads = (int)(num / denom);
				xsectors = a1 / (c1 * xheads + h1);
				break;
			}
		}
		if (xheads != -1)
			break;
	}

	if (xheads == -1)
		return -1;

	/*
	 * Estimate the number of cylinders.
	 * XXX relies on get_disks having been called.
	 */
	xcylinders = dlsize / xheads / xsectors;
	if (dlsize != xcylinders * xheads * xsectors)
		xcylinders++;

	/*
	 * Now verify consistency with each of the partition table entries.
	 * Be willing to shove cylinders up a little bit to make things work,
	 * but translation mismatches are fatal.
	 */
	for (i = 0; i < MBR_PART_COUNT * 2; i++) {
		if (get_mapping(parts, i, &c1, &h1, &s1, &a1) < 0)
			continue;
		if (c1 >= MAXCYL - 1)
			/* Ignore anything that is near the CHS limit */
			continue;
		if (xsectors * (c1 * xheads + h1) + s1 != a1)
			return -1;
	}

	/*
	 * Everything checks out.  Reset the geometry to use for further
	 * calculations.
	 */
	*cyl = MIN(xcylinders, MAXCYL);
	*head = xheads;
	*sec = xsectors;
	return 0;
}

static int
get_mapping(struct mbr_partition *parts, int i,
	    int *cylinder, int *head, int *sector, unsigned long *absolute)
{
	struct mbr_partition *apart = &parts[i / 2];

	if (apart->mbrp_type == 0)
		return -1;
	if (i % 2 == 0) {
		*cylinder = MBR_PCYL(apart->mbrp_scyl, apart->mbrp_ssect);
		*head = apart->mbrp_shd;
		*sector = MBR_PSECT(apart->mbrp_ssect) - 1;
		*absolute = le32toh(apart->mbrp_start);
	} else {
		*cylinder = MBR_PCYL(apart->mbrp_ecyl, apart->mbrp_esect);
		*head = apart->mbrp_ehd;
		*sector = MBR_PSECT(apart->mbrp_esect) - 1;
		*absolute = le32toh(apart->mbrp_start)
			+ le32toh(apart->mbrp_size) - 1;
	}
	/* Sanity check the data against max values */
	if ((((*cylinder * MAXHEAD) + *head) * MAXSECTOR + *sector) < *absolute)
		/* cannot be a CHS mapping */
		return -1;

	return 0;
}