diff --git a/sys/boot/common/disk.c b/sys/boot/common/disk.c
index e64afa973196..e45514c8d000 100644
--- a/sys/boot/common/disk.c
+++ b/sys/boot/common/disk.c
@@ -1,534 +1,563 @@
 /*-
  * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
  * Copyright (c) 2012 Andrey V. Elsukov <ae@FreeBSD.org>
  * All rights reserved.
  *
  * 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.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 THE AUTHOR OR CONTRIBUTORS 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.
  */
 
 #include <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
 #include <sys/disk.h>
 #include <sys/queue.h>
 #include <stand.h>
 #include <stdarg.h>
 #include <bootstrap.h>
 #include <part.h>
 
 #include "disk.h"
 
 #ifdef DISK_DEBUG
 # define DEBUG(fmt, args...)	printf("%s: " fmt "\n" , __func__ , ## args)
 #else
 # define DEBUG(fmt, args...)
 #endif
 
 struct open_disk {
 	struct ptable		*table;
 	uint64_t		mediasize;
+	uint64_t		entrysize;
 	u_int			sectorsize;
 	u_int			flags;
 	int			rcnt;
 };
 
 struct print_args {
 	struct disk_devdesc	*dev;
 	const char		*prefix;
 	int			verbose;
 };
 
 struct dentry {
 	const struct devsw	*d_dev;
 	int			d_unit;
 	int			d_slice;
 	int			d_partition;
 
 	struct open_disk	*od;
 	uint64_t		d_offset;
 	STAILQ_ENTRY(dentry)	entry;
 #ifdef DISK_DEBUG
 	uint32_t		count;
 #endif
 };
 
 static STAILQ_HEAD(, dentry) opened_disks =
     STAILQ_HEAD_INITIALIZER(opened_disks);
 
 static int
 disk_lookup(struct disk_devdesc *dev)
 {
 	struct dentry *entry;
 	int rc;
 
 	rc = ENOENT;
 	STAILQ_FOREACH(entry, &opened_disks, entry) {
 		if (entry->d_dev != dev->d_dev ||
 		    entry->d_unit != dev->d_unit)
 			continue;
 		dev->d_opendata = entry->od;
 		if (entry->d_slice == dev->d_slice &&
 		    entry->d_partition == dev->d_partition) {
 			dev->d_offset = entry->d_offset;
 			DEBUG("%s offset %lld", disk_fmtdev(dev),
 			    (long long)dev->d_offset);
 #ifdef DISK_DEBUG
 			entry->count++;
 #endif
 			return (0);
 		}
 		rc = EAGAIN;
 	}
 	return (rc);
 }
 
 static void
 disk_insert(struct disk_devdesc *dev)
 {
 	struct dentry *entry;
 
 	entry = (struct dentry *)malloc(sizeof(struct dentry));
 	if (entry == NULL) {
 		DEBUG("no memory");
 		return;
 	}
 	entry->d_dev = dev->d_dev;
 	entry->d_unit = dev->d_unit;
 	entry->d_slice = dev->d_slice;
 	entry->d_partition = dev->d_partition;
 	entry->od = (struct open_disk *)dev->d_opendata;
 	entry->od->rcnt++;
 	entry->d_offset = dev->d_offset;
 #ifdef DISK_DEBUG
 	entry->count = 1;
 #endif
 	STAILQ_INSERT_TAIL(&opened_disks, entry, entry);
 	DEBUG("%s cached", disk_fmtdev(dev));
 }
 
 #ifdef DISK_DEBUG
 COMMAND_SET(dcachestat, "dcachestat", "get disk cache stats",
     command_dcachestat);
 
 static int
 command_dcachestat(int argc, char *argv[])
 {
 	struct disk_devdesc dev;
 	struct dentry *entry;
 
 	STAILQ_FOREACH(entry, &opened_disks, entry) {
 		dev.d_dev = (struct devsw *)entry->d_dev;
 		dev.d_unit = entry->d_unit;
 		dev.d_slice = entry->d_slice;
 		dev.d_partition = entry->d_partition;
 		printf("%s %d => %p [%d]\n", disk_fmtdev(&dev), entry->count,
 		    entry->od, entry->od->rcnt);
 	}
 	return (CMD_OK);
 }
 #endif /* DISK_DEBUG */
 
 /* Convert size to a human-readable number. */
 static char *
 display_size(uint64_t size, u_int sectorsize)
 {
 	static char buf[80];
 	char unit;
 
 	size = size * sectorsize / 1024;
 	unit = 'K';
 	if (size >= 10485760000LL) {
 		size /= 1073741824;
 		unit = 'T';
 	} else if (size >= 10240000) {
 		size /= 1048576;
 		unit = 'G';
 	} else if (size >= 10000) {
 		size /= 1024;
 		unit = 'M';
 	}
 	sprintf(buf, "%ld%cB", (long)size, unit);
 	return (buf);
 }
 
 int
 ptblread(void *d, void *buf, size_t blocks, uint64_t offset)
 {
 	struct disk_devdesc *dev;
 	struct open_disk *od;
 
 	dev = (struct disk_devdesc *)d;
 	od = (struct open_disk *)dev->d_opendata;
 	return (dev->d_dev->dv_strategy(dev, F_READ, offset,
 	    blocks * od->sectorsize, (char *)buf, NULL));
 }
 
 #define	PWIDTH	35
 static int
 ptable_print(void *arg, const char *pname, const struct ptable_entry *part)
 {
 	struct print_args *pa, bsd;
 	struct open_disk *od;
 	struct ptable *table;
 	char line[80];
 	int res;
 
 	pa = (struct print_args *)arg;
 	od = (struct open_disk *)pa->dev->d_opendata;
 	sprintf(line, "  %s%s: %s", pa->prefix, pname,
 	    parttype2str(part->type));
 	if (pa->verbose)
 		sprintf(line, "%-*s%s", PWIDTH, line,
 		    display_size(part->end - part->start + 1,
 		    od->sectorsize));
 	strcat(line, "\n");
 	if (pager_output(line))
 		return 1;
 	res = 0;
 	if (part->type == PART_FREEBSD) {
 		/* Open slice with BSD label */
 		pa->dev->d_offset = part->start;
 		table = ptable_open(pa->dev, part->end - part->start + 1,
 		    od->sectorsize, ptblread);
 		if (table == NULL)
 			return 0;
 		sprintf(line, "  %s%s", pa->prefix, pname);
 		bsd.dev = pa->dev;
 		bsd.prefix = line;
 		bsd.verbose = pa->verbose;
 		res = ptable_iterate(table, &bsd, ptable_print);
 		ptable_close(table);
 	}
 
 	return (res);
 }
 #undef PWIDTH
 
 int
 disk_print(struct disk_devdesc *dev, char *prefix, int verbose)
 {
 	struct open_disk *od;
 	struct print_args pa;
 
 	/* Disk should be opened */
 	od = (struct open_disk *)dev->d_opendata;
 	pa.dev = dev;
 	pa.prefix = prefix;
 	pa.verbose = verbose;
 	return (ptable_iterate(od->table, &pa, ptable_print));
 }
 
 int
 disk_read(struct disk_devdesc *dev, void *buf, uint64_t offset, u_int blocks)
 {
 	struct open_disk *od;
 	int ret;
 
 	od = (struct open_disk *)dev->d_opendata;
 	ret = dev->d_dev->dv_strategy(dev, F_READ, dev->d_offset + offset,
 	    blocks * od->sectorsize, buf, NULL);
 
 	return (ret);
 }
 
 int
 disk_write(struct disk_devdesc *dev, void *buf, uint64_t offset, u_int blocks)
 {
 	struct open_disk *od;
 	int ret;
 
 	od = (struct open_disk *)dev->d_opendata;
 	ret = dev->d_dev->dv_strategy(dev, F_WRITE, dev->d_offset + offset,
 	    blocks * od->sectorsize, buf, NULL);
 
 	return (ret);
 }
 
 int
-disk_ioctl(struct disk_devdesc *dev, u_long cmd, void *buf)
+disk_ioctl(struct disk_devdesc *dev, u_long cmd, void *data)
 {
+	struct open_disk *od = dev->d_opendata;
+
+	if (od == NULL)
+		return (ENOTTY);
+
+	switch (cmd) {
+	case DIOCGSECTORSIZE:
+		*(u_int *)data = od->sectorsize;
+		break;
+	case DIOCGMEDIASIZE:
+		if (dev->d_offset == 0)
+			*(uint64_t *)data = od->mediasize;
+		else
+			*(uint64_t *)data = od->entrysize * od->sectorsize;
+		break;
+	default:
+		return (ENOTTY);
+	}
 
-	if (dev->d_dev->dv_ioctl)
-		return ((*dev->d_dev->dv_ioctl)(dev->d_opendata, cmd, buf));
-
-	return (ENXIO);
+	return (0);
 }
 
 int
 disk_open(struct disk_devdesc *dev, uint64_t mediasize, u_int sectorsize,
     u_int flags)
 {
 	struct open_disk *od;
 	struct ptable *table;
 	struct ptable_entry part;
 	int rc, slice, partition;
 
 	rc = 0;
 	if ((flags & DISK_F_NOCACHE) == 0) {
 		rc = disk_lookup(dev);
 		if (rc == 0)
 			return (0);
 	}
 	/*
 	 * While we are reading disk metadata, make sure we do it relative
 	 * to the start of the disk
 	 */
 	dev->d_offset = 0;
 	table = NULL;
 	slice = dev->d_slice;
 	partition = dev->d_partition;
 	if (rc == EAGAIN) {
 		/*
 		 * This entire disk was already opened and there is no
 		 * need to allocate new open_disk structure and open the
 		 * main partition table.
 		 */
 		od = (struct open_disk *)dev->d_opendata;
 		DEBUG("%s unit %d, slice %d, partition %d => %p (cached)",
 		    disk_fmtdev(dev), dev->d_unit, dev->d_slice,
 		    dev->d_partition, od);
 		goto opened;
 	} else {
 		od = (struct open_disk *)malloc(sizeof(struct open_disk));
 		if (od == NULL) {
 			DEBUG("no memory");
 			return (ENOMEM);
 		}
 		dev->d_opendata = od;
 		od->rcnt = 0;
+		od->entrysize = 0;
 	}
 	od->mediasize = mediasize;
 	od->sectorsize = sectorsize;
 	od->flags = flags;
 	DEBUG("%s unit %d, slice %d, partition %d => %p",
 	    disk_fmtdev(dev), dev->d_unit, dev->d_slice, dev->d_partition, od);
 
 	/* Determine disk layout. */
 	od->table = ptable_open(dev, mediasize / sectorsize, sectorsize,
 	    ptblread);
 	if (od->table == NULL) {
 		DEBUG("Can't read partition table");
 		rc = ENXIO;
 		goto out;
 	}
+
+	if (ptable_getsize(od->table, &mediasize) != 0) {
+		rc = ENXIO;
+		goto out;
+	}
+	if (mediasize > od->mediasize) {
+		od->mediasize = mediasize;
+	}
 opened:
 	rc = 0;
 	if (ptable_gettype(od->table) == PTABLE_BSD &&
 	    partition >= 0) {
 		/* It doesn't matter what value has d_slice */
 		rc = ptable_getpart(od->table, &part, partition);
-		if (rc == 0)
+		if (rc == 0) {
 			dev->d_offset = part.start;
+			od->entrysize = part.end - part.start + 1;
+		}
 	} else if (slice >= 0) {
 		/* Try to get information about partition */
 		if (slice == 0)
 			rc = ptable_getbestpart(od->table, &part);
 		else
 			rc = ptable_getpart(od->table, &part, slice);
 		if (rc != 0) /* Partition doesn't exist */
 			goto out;
 		dev->d_offset = part.start;
+		od->entrysize = part.end - part.start + 1;
 		slice = part.index;
 		if (ptable_gettype(od->table) == PTABLE_GPT) {
 			partition = 255;
 			goto out; /* Nothing more to do */
 		} else if (partition == 255) {
 			/*
 			 * When we try to open GPT partition, but partition
 			 * table isn't GPT, reset d_partition value to -1
 			 * and try to autodetect appropriate value.
 			 */
 			partition = -1;
 		}
 		/*
 		 * If d_partition < 0 and we are looking at a BSD slice,
 		 * then try to read BSD label, otherwise return the
 		 * whole MBR slice.
 		 */
 		if (partition == -1 &&
 		    part.type != PART_FREEBSD)
 			goto out;
 		/* Try to read BSD label */
 		table = ptable_open(dev, part.end - part.start + 1,
 		    od->sectorsize, ptblread);
 		if (table == NULL) {
 			DEBUG("Can't read BSD label");
 			rc = ENXIO;
 			goto out;
 		}
 		/*
 		 * If slice contains BSD label and d_partition < 0, then
 		 * assume the 'a' partition. Otherwise just return the
 		 * whole MBR slice, because it can contain ZFS.
 		 */
 		if (partition < 0) {
 			if (ptable_gettype(table) != PTABLE_BSD)
 				goto out;
 			partition = 0;
 		}
 		rc = ptable_getpart(table, &part, partition);
 		if (rc != 0)
 			goto out;
 		dev->d_offset += part.start;
+		od->entrysize = part.end - part.start + 1;
 	}
 out:
 	if (table != NULL)
 		ptable_close(table);
 
 	if (rc != 0) {
 		if (od->rcnt < 1) {
 			if (od->table != NULL)
 				ptable_close(od->table);
 			free(od);
 		}
 		DEBUG("%s could not open", disk_fmtdev(dev));
 	} else {
 		if ((flags & DISK_F_NOCACHE) == 0)
 			disk_insert(dev);
 		/* Save the slice and partition number to the dev */
 		dev->d_slice = slice;
 		dev->d_partition = partition;
 		DEBUG("%s offset %lld => %p", disk_fmtdev(dev),
 		    (long long)dev->d_offset, od);
 	}
 	return (rc);
 }
 
 int
 disk_close(struct disk_devdesc *dev)
 {
 	struct open_disk *od;
 
 	od = (struct open_disk *)dev->d_opendata;
 	DEBUG("%s closed => %p [%d]", disk_fmtdev(dev), od, od->rcnt);
 	if (od->flags & DISK_F_NOCACHE) {
 		ptable_close(od->table);
 		free(od);
 	}
 	return (0);
 }
 
 void
 disk_cleanup(const struct devsw *d_dev)
 {
 #ifdef DISK_DEBUG
 	struct disk_devdesc dev;
 #endif
 	struct dentry *entry, *tmp;
 
 	STAILQ_FOREACH_SAFE(entry, &opened_disks, entry, tmp) {
 		if (entry->d_dev != d_dev)
 			continue;
 		entry->od->rcnt--;
 #ifdef DISK_DEBUG
 		dev.d_dev = (struct devsw *)entry->d_dev;
 		dev.d_unit = entry->d_unit;
 		dev.d_slice = entry->d_slice;
 		dev.d_partition = entry->d_partition;
 		DEBUG("%s was freed => %p [%d]", disk_fmtdev(&dev),
 		    entry->od, entry->od->rcnt);
 #endif
 		STAILQ_REMOVE(&opened_disks, entry, dentry, entry);
 		if (entry->od->rcnt < 1) {
 			if (entry->od->table != NULL)
 				ptable_close(entry->od->table);
 			free(entry->od);
 		}
 		free(entry);
 	}
 }
 
 char*
 disk_fmtdev(struct disk_devdesc *dev)
 {
 	static char buf[128];
 	char *cp;
 
 	cp = buf + sprintf(buf, "%s%d", dev->d_dev->dv_name, dev->d_unit);
 	if (dev->d_slice >= 0) {
 #ifdef LOADER_GPT_SUPPORT
 		if (dev->d_partition == 255) {
 			sprintf(cp, "p%d:", dev->d_slice);
 			return (buf);
 		} else
 #endif
 #ifdef LOADER_MBR_SUPPORT
 			cp += sprintf(cp, "s%d", dev->d_slice);
 #endif
 	}
 	if (dev->d_partition >= 0)
 		cp += sprintf(cp, "%c", dev->d_partition + 'a');
 	strcat(cp, ":");
 	return (buf);
 }
 
 int
 disk_parsedev(struct disk_devdesc *dev, const char *devspec, const char **path)
 {
 	int unit, slice, partition;
 	const char *np;
 	char *cp;
 
 	np = devspec;
 	unit = slice = partition = -1;
 	if (*np != '\0' && *np != ':') {
 		unit = strtol(np, &cp, 10);
 		if (cp == np)
 			return (EUNIT);
 #ifdef LOADER_GPT_SUPPORT
 		if (*cp == 'p') {
 			np = cp + 1;
 			slice = strtol(np, &cp, 10);
 			if (np == cp)
 				return (ESLICE);
 			/* we don't support nested partitions on GPT */
 			if (*cp != '\0' && *cp != ':')
 				return (EINVAL);
 			partition = 255;
 		} else
 #endif
 #ifdef LOADER_MBR_SUPPORT
 		if (*cp == 's') {
 			np = cp + 1;
 			slice = strtol(np, &cp, 10);
 			if (np == cp)
 				return (ESLICE);
 		}
 #endif
 		if (*cp != '\0' && *cp != ':') {
 			partition = *cp - 'a';
 			if (partition < 0)
 				return (EPART);
 			cp++;
 		}
 	} else
 		return (EINVAL);
 
 	if (*cp != '\0' && *cp != ':')
 		return (EINVAL);
 	dev->d_unit = unit;
 	dev->d_slice = slice;
 	dev->d_partition = partition;
 	if (path != NULL)
 		*path = (*cp == '\0') ? cp: cp + 1;
 	return (0);
 }
diff --git a/sys/boot/common/part.c b/sys/boot/common/part.c
index d26bccf1cc68..fac599e64e34 100644
--- a/sys/boot/common/part.c
+++ b/sys/boot/common/part.c
@@ -1,864 +1,897 @@
 /*-
  * Copyright (c) 2012 Andrey V. Elsukov <ae@FreeBSD.org>
  * All rights reserved.
  *
  * 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.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``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 THE AUTHORS OR CONTRIBUTORS 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.
  */
 
 #include <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
 #include <stand.h>
 #include <sys/param.h>
 #include <sys/diskmbr.h>
 #include <sys/disklabel.h>
 #include <sys/endian.h>
 #include <sys/gpt.h>
 #include <sys/stddef.h>
 #include <sys/queue.h>
 #include <sys/vtoc.h>
 
 #include <crc32.h>
 #include <part.h>
 #include <uuid.h>
 
 #ifdef PART_DEBUG
 #define	DEBUG(fmt, args...) printf("%s: " fmt "\n" , __func__ , ## args)
 #else
 #define	DEBUG(fmt, args...)
 #endif
 
 #ifdef LOADER_GPT_SUPPORT
 #define	MAXTBLSZ	64
 static const uuid_t gpt_uuid_unused = GPT_ENT_TYPE_UNUSED;
 static const uuid_t gpt_uuid_ms_basic_data = GPT_ENT_TYPE_MS_BASIC_DATA;
 static const uuid_t gpt_uuid_freebsd_ufs = GPT_ENT_TYPE_FREEBSD_UFS;
 static const uuid_t gpt_uuid_efi = GPT_ENT_TYPE_EFI;
 static const uuid_t gpt_uuid_freebsd = GPT_ENT_TYPE_FREEBSD;
 static const uuid_t gpt_uuid_freebsd_boot = GPT_ENT_TYPE_FREEBSD_BOOT;
 static const uuid_t gpt_uuid_freebsd_nandfs = GPT_ENT_TYPE_FREEBSD_NANDFS;
 static const uuid_t gpt_uuid_freebsd_swap = GPT_ENT_TYPE_FREEBSD_SWAP;
 static const uuid_t gpt_uuid_freebsd_zfs = GPT_ENT_TYPE_FREEBSD_ZFS;
 static const uuid_t gpt_uuid_freebsd_vinum = GPT_ENT_TYPE_FREEBSD_VINUM;
 #endif
 
 struct pentry {
 	struct ptable_entry	part;
 	uint64_t		flags;
 	union {
 		uint8_t bsd;
 		uint8_t	mbr;
 		uuid_t	gpt;
 		uint16_t vtoc8;
 	} type;
 	STAILQ_ENTRY(pentry)	entry;
 };
 
 struct ptable {
 	enum ptable_type	type;
 	uint16_t		sectorsize;
 	uint64_t		sectors;
 
 	STAILQ_HEAD(, pentry)	entries;
 };
 
 static struct parttypes {
 	enum partition_type	type;
 	const char		*desc;
 } ptypes[] = {
 	{ PART_UNKNOWN,		"Unknown" },
 	{ PART_EFI,		"EFI" },
 	{ PART_FREEBSD,		"FreeBSD" },
 	{ PART_FREEBSD_BOOT,	"FreeBSD boot" },
 	{ PART_FREEBSD_NANDFS,	"FreeBSD nandfs" },
 	{ PART_FREEBSD_UFS,	"FreeBSD UFS" },
 	{ PART_FREEBSD_ZFS,	"FreeBSD ZFS" },
 	{ PART_FREEBSD_SWAP,	"FreeBSD swap" },
 	{ PART_FREEBSD_VINUM,	"FreeBSD vinum" },
 	{ PART_LINUX,		"Linux" },
 	{ PART_LINUX_SWAP,	"Linux swap" },
 	{ PART_DOS,		"DOS/Windows" },
 };
 
 const char *
 parttype2str(enum partition_type type)
 {
 	size_t i;
 
 	for (i = 0; i < nitems(ptypes); i++)
 		if (ptypes[i].type == type)
 			return (ptypes[i].desc);
 	return (ptypes[0].desc);
 }
 
 #ifdef LOADER_GPT_SUPPORT
 static void
 uuid_letoh(uuid_t *uuid)
 {
 
 	uuid->time_low = le32toh(uuid->time_low);
 	uuid->time_mid = le16toh(uuid->time_mid);
 	uuid->time_hi_and_version = le16toh(uuid->time_hi_and_version);
 }
 
 static enum partition_type
 gpt_parttype(uuid_t type)
 {
 
 	if (uuid_equal(&type, &gpt_uuid_efi, NULL))
 		return (PART_EFI);
 	else if (uuid_equal(&type, &gpt_uuid_ms_basic_data, NULL))
 		return (PART_DOS);
 	else if (uuid_equal(&type, &gpt_uuid_freebsd_boot, NULL))
 		return (PART_FREEBSD_BOOT);
 	else if (uuid_equal(&type, &gpt_uuid_freebsd_ufs, NULL))
 		return (PART_FREEBSD_UFS);
 	else if (uuid_equal(&type, &gpt_uuid_freebsd_zfs, NULL))
 		return (PART_FREEBSD_ZFS);
 	else if (uuid_equal(&type, &gpt_uuid_freebsd_swap, NULL))
 		return (PART_FREEBSD_SWAP);
 	else if (uuid_equal(&type, &gpt_uuid_freebsd_vinum, NULL))
 		return (PART_FREEBSD_VINUM);
 	else if (uuid_equal(&type, &gpt_uuid_freebsd_nandfs, NULL))
 		return (PART_FREEBSD_NANDFS);
 	else if (uuid_equal(&type, &gpt_uuid_freebsd, NULL))
 		return (PART_FREEBSD);
 	return (PART_UNKNOWN);
 }
 
 static struct gpt_hdr*
 gpt_checkhdr(struct gpt_hdr *hdr, uint64_t lba_self, uint64_t lba_last,
     uint16_t sectorsize)
 {
 	uint32_t sz, crc;
 
 	if (memcmp(hdr->hdr_sig, GPT_HDR_SIG, sizeof(hdr->hdr_sig)) != 0) {
 		DEBUG("no GPT signature");
 		return (NULL);
 	}
 	sz = le32toh(hdr->hdr_size);
 	if (sz < 92 || sz > sectorsize) {
 		DEBUG("invalid GPT header size: %d", sz);
 		return (NULL);
 	}
 	crc = le32toh(hdr->hdr_crc_self);
 	hdr->hdr_crc_self = 0;
 	if (crc32(hdr, sz) != crc) {
 		DEBUG("GPT header's CRC doesn't match");
 		return (NULL);
 	}
 	hdr->hdr_crc_self = crc;
 	hdr->hdr_revision = le32toh(hdr->hdr_revision);
 	if (hdr->hdr_revision < GPT_HDR_REVISION) {
 		DEBUG("unsupported GPT revision %d", hdr->hdr_revision);
 		return (NULL);
 	}
 	hdr->hdr_lba_self = le64toh(hdr->hdr_lba_self);
 	if (hdr->hdr_lba_self != lba_self) {
 		DEBUG("self LBA doesn't match");
 		return (NULL);
 	}
 	hdr->hdr_lba_alt = le64toh(hdr->hdr_lba_alt);
 	if (hdr->hdr_lba_alt == hdr->hdr_lba_self) {
 		DEBUG("invalid alternate LBA");
 		return (NULL);
 	}
 	hdr->hdr_entries = le32toh(hdr->hdr_entries);
 	hdr->hdr_entsz = le32toh(hdr->hdr_entsz);
 	if (hdr->hdr_entries == 0 ||
 	    hdr->hdr_entsz < sizeof(struct gpt_ent) ||
 	    sectorsize % hdr->hdr_entsz != 0) {
 		DEBUG("invalid entry size or number of entries");
 		return (NULL);
 	}
 	hdr->hdr_lba_start = le64toh(hdr->hdr_lba_start);
 	hdr->hdr_lba_end = le64toh(hdr->hdr_lba_end);
 	hdr->hdr_lba_table = le64toh(hdr->hdr_lba_table);
 	hdr->hdr_crc_table = le32toh(hdr->hdr_crc_table);
 	uuid_letoh(&hdr->hdr_uuid);
 	return (hdr);
 }
 
 static int
 gpt_checktbl(const struct gpt_hdr *hdr, u_char *tbl, size_t size,
     uint64_t lba_last)
 {
 	struct gpt_ent *ent;
 	uint32_t i, cnt;
 
 	cnt = size / hdr->hdr_entsz;
 	if (hdr->hdr_entries <= cnt) {
 		cnt = hdr->hdr_entries;
 		/* Check CRC only when buffer size is enough for table. */
 		if (hdr->hdr_crc_table !=
 		    crc32(tbl, hdr->hdr_entries * hdr->hdr_entsz)) {
 			DEBUG("GPT table's CRC doesn't match");
 			return (-1);
 		}
 	}
 	for (i = 0; i < cnt; i++) {
 		ent = (struct gpt_ent *)(tbl + i * hdr->hdr_entsz);
 		uuid_letoh(&ent->ent_type);
 		if (uuid_equal(&ent->ent_type, &gpt_uuid_unused, NULL))
 			continue;
 		ent->ent_lba_start = le64toh(ent->ent_lba_start);
 		ent->ent_lba_end = le64toh(ent->ent_lba_end);
 	}
 	return (0);
 }
 
 static struct ptable*
 ptable_gptread(struct ptable *table, void *dev, diskread_t dread)
 {
 	struct pentry *entry;
 	struct gpt_hdr *phdr, hdr;
 	struct gpt_ent *ent;
 	u_char *buf, *tbl;
 	uint64_t offset;
 	int pri, sec;
 	size_t size, i;
 
 	buf = malloc(table->sectorsize);
 	if (buf == NULL)
 		return (NULL);
 	tbl = malloc(table->sectorsize * MAXTBLSZ);
 	if (tbl == NULL) {
 		free(buf);
 		return (NULL);
 	}
 	/* Read the primary GPT header. */
 	if (dread(dev, buf, 1, 1) != 0) {
 		ptable_close(table);
 		table = NULL;
 		goto out;
 	}
 	pri = sec = 0;
 	/* Check the primary GPT header. */
 	phdr = gpt_checkhdr((struct gpt_hdr *)buf, 1, table->sectors - 1,
 	    table->sectorsize);
 	if (phdr != NULL) {
 		/* Read the primary GPT table. */
 		size = MIN(MAXTBLSZ,
 		    howmany(phdr->hdr_entries * phdr->hdr_entsz,
 		        table->sectorsize));
 		if (dread(dev, tbl, size, phdr->hdr_lba_table) == 0 &&
 		    gpt_checktbl(phdr, tbl, size * table->sectorsize,
 		    table->sectors - 1) == 0) {
 			memcpy(&hdr, phdr, sizeof(hdr));
 			pri = 1;
 		}
 	}
 	offset = pri ? hdr.hdr_lba_alt: table->sectors - 1;
 	/* Read the backup GPT header. */
 	if (dread(dev, buf, 1, offset) != 0)
 		phdr = NULL;
 	else
 		phdr = gpt_checkhdr((struct gpt_hdr *)buf, offset,
 		    table->sectors - 1, table->sectorsize);
 	if (phdr != NULL) {
 		/*
 		 * Compare primary and backup headers.
 		 * If they are equal, then we do not need to read backup
 		 * table. If they are different, then prefer backup header
 		 * and try to read backup table.
 		 */
 		if (pri == 0 ||
 		    uuid_equal(&hdr.hdr_uuid, &phdr->hdr_uuid, NULL) == 0 ||
 		    hdr.hdr_revision != phdr->hdr_revision ||
 		    hdr.hdr_size != phdr->hdr_size ||
 		    hdr.hdr_lba_start != phdr->hdr_lba_start ||
 		    hdr.hdr_lba_end != phdr->hdr_lba_end ||
 		    hdr.hdr_entries != phdr->hdr_entries ||
 		    hdr.hdr_entsz != phdr->hdr_entsz ||
 		    hdr.hdr_crc_table != phdr->hdr_crc_table) {
 			/* Read the backup GPT table. */
 			size = MIN(MAXTBLSZ,
 				   howmany(phdr->hdr_entries * phdr->hdr_entsz,
 				       table->sectorsize));
 			if (dread(dev, tbl, size, phdr->hdr_lba_table) == 0 &&
 			    gpt_checktbl(phdr, tbl, size * table->sectorsize,
 			    table->sectors - 1) == 0) {
 				memcpy(&hdr, phdr, sizeof(hdr));
 				sec = 1;
 			}
 		}
 	}
 	if (pri == 0 && sec == 0) {
 		/* Both primary and backup tables are invalid. */
 		table->type = PTABLE_NONE;
 		goto out;
 	}
 	DEBUG("GPT detected");
 	size = MIN(hdr.hdr_entries * hdr.hdr_entsz,
 	    MAXTBLSZ * table->sectorsize);
+
+	/*
+	 * If the disk's sector count is smaller than the sector count recorded
+	 * in the disk's GPT table header, set the table->sectors to the value
+	 * recorded in GPT tables. This is done to work around buggy firmware
+	 * that returns truncated disk sizes.
+	 *
+	 * Note, this is still not a foolproof way to get disk's size. For
+	 * example, an image file can be truncated when copied to smaller media.
+	 */
+	if (hdr.hdr_lba_alt + 1 > table->sectors)
+		table->sectors = hdr.hdr_lba_alt + 1;
+
 	for (i = 0; i < size / hdr.hdr_entsz; i++) {
 		ent = (struct gpt_ent *)(tbl + i * hdr.hdr_entsz);
 		if (uuid_equal(&ent->ent_type, &gpt_uuid_unused, NULL))
 			continue;
+
+		/* Simple sanity checks. */
+		if (ent->ent_lba_start < hdr.hdr_lba_start ||
+		    ent->ent_lba_end > hdr.hdr_lba_end ||
+		    ent->ent_lba_start > ent->ent_lba_end)
+			continue;
+
 		entry = malloc(sizeof(*entry));
 		if (entry == NULL)
 			break;
 		entry->part.start = ent->ent_lba_start;
 		entry->part.end = ent->ent_lba_end;
 		entry->part.index = i + 1;
 		entry->part.type = gpt_parttype(ent->ent_type);
 		entry->flags = le64toh(ent->ent_attr);
 		memcpy(&entry->type.gpt, &ent->ent_type, sizeof(uuid_t));
 		STAILQ_INSERT_TAIL(&table->entries, entry, entry);
 		DEBUG("new GPT partition added");
 	}
 out:
 	free(buf);
 	free(tbl);
 	return (table);
 }
 #endif /* LOADER_GPT_SUPPORT */
 
 #ifdef LOADER_MBR_SUPPORT
 /* We do not need to support too many EBR partitions in the loader */
 #define	MAXEBRENTRIES		8
 static enum partition_type
 mbr_parttype(uint8_t type)
 {
 
 	switch (type) {
 	case DOSPTYP_386BSD:
 		return (PART_FREEBSD);
 	case DOSPTYP_LINSWP:
 		return (PART_LINUX_SWAP);
 	case DOSPTYP_LINUX:
 		return (PART_LINUX);
 	case 0x01:
 	case 0x04:
 	case 0x06:
 	case 0x07:
 	case 0x0b:
 	case 0x0c:
 	case 0x0e:
 		return (PART_DOS);
 	}
 	return (PART_UNKNOWN);
 }
 
 static struct ptable*
 ptable_ebrread(struct ptable *table, void *dev, diskread_t dread)
 {
 	struct dos_partition *dp;
 	struct pentry *e1, *entry;
 	uint32_t start, end, offset;
 	u_char *buf;
 	int i, index;
 
 	STAILQ_FOREACH(e1, &table->entries, entry) {
 		if (e1->type.mbr == DOSPTYP_EXT ||
 		    e1->type.mbr == DOSPTYP_EXTLBA)
 			break;
 	}
 	if (e1 == NULL)
 		return (table);
 	index = 5;
 	offset = e1->part.start;
 	buf = malloc(table->sectorsize);
 	if (buf == NULL)
 		return (table);
 	DEBUG("EBR detected");
 	for (i = 0; i < MAXEBRENTRIES; i++) {
 #if 0	/* Some BIOSes return an incorrect number of sectors */
 		if (offset >= table->sectors)
 			break;
 #endif
 		if (dread(dev, buf, 1, offset) != 0)
 			break;
 		dp = (struct dos_partition *)(buf + DOSPARTOFF);
 		if (dp[0].dp_typ == 0)
 			break;
 		start = le32toh(dp[0].dp_start);
 		if (dp[0].dp_typ == DOSPTYP_EXT &&
 		    dp[1].dp_typ == 0) {
 			offset = e1->part.start + start;
 			continue;
 		}
 		end = le32toh(dp[0].dp_size);
 		entry = malloc(sizeof(*entry));
 		if (entry == NULL)
 			break;
 		entry->part.start = offset + start;
 		entry->part.end = entry->part.start + end - 1;
 		entry->part.index = index++;
 		entry->part.type = mbr_parttype(dp[0].dp_typ);
 		entry->flags = dp[0].dp_flag;
 		entry->type.mbr = dp[0].dp_typ;
 		STAILQ_INSERT_TAIL(&table->entries, entry, entry);
 		DEBUG("new EBR partition added");
 		if (dp[1].dp_typ == 0)
 			break;
 		offset = e1->part.start + le32toh(dp[1].dp_start);
 	}
 	free(buf);
 	return (table);
 }
 #endif /* LOADER_MBR_SUPPORT */
 
 static enum partition_type
 bsd_parttype(uint8_t type)
 {
 
 	switch (type) {
 	case FS_NANDFS:
 		return (PART_FREEBSD_NANDFS);
 	case FS_SWAP:
 		return (PART_FREEBSD_SWAP);
 	case FS_BSDFFS:
 		return (PART_FREEBSD_UFS);
 	case FS_VINUM:
 		return (PART_FREEBSD_VINUM);
 	case FS_ZFS:
 		return (PART_FREEBSD_ZFS);
 	}
 	return (PART_UNKNOWN);
 }
 
 static struct ptable*
 ptable_bsdread(struct ptable *table, void *dev, diskread_t dread)
 {
 	struct disklabel *dl;
 	struct partition *part;
 	struct pentry *entry;
 	u_char *buf;
 	uint32_t raw_offset;
 	int i;
 
 	if (table->sectorsize < sizeof(struct disklabel)) {
 		DEBUG("Too small sectorsize");
 		return (table);
 	}
 	buf = malloc(table->sectorsize);
 	if (buf == NULL)
 		return (table);
 	if (dread(dev, buf, 1, 1) != 0) {
 		DEBUG("read failed");
 		ptable_close(table);
 		table = NULL;
 		goto out;
 	}
 	dl = (struct disklabel *)buf;
 	if (le32toh(dl->d_magic) != DISKMAGIC &&
 	    le32toh(dl->d_magic2) != DISKMAGIC)
 		goto out;
 	if (le32toh(dl->d_secsize) != table->sectorsize) {
 		DEBUG("unsupported sector size");
 		goto out;
 	}
 	dl->d_npartitions = le16toh(dl->d_npartitions);
 	if (dl->d_npartitions > 20 || dl->d_npartitions < 8) {
 		DEBUG("invalid number of partitions");
 		goto out;
 	}
 	DEBUG("BSD detected");
 	part = &dl->d_partitions[0];
 	raw_offset = le32toh(part[RAW_PART].p_offset);
 	for (i = 0; i < dl->d_npartitions; i++, part++) {
 		if (i == RAW_PART)
 			continue;
 		if (part->p_size == 0)
 			continue;
 		entry = malloc(sizeof(*entry));
 		if (entry == NULL)
 			break;
 		entry->part.start = le32toh(part->p_offset) - raw_offset;
 		entry->part.end = entry->part.start +
 		    le32toh(part->p_size) + 1;
 		entry->part.type = bsd_parttype(part->p_fstype);
 		entry->part.index = i; /* starts from zero */
 		entry->type.bsd = part->p_fstype;
 		STAILQ_INSERT_TAIL(&table->entries, entry, entry);
 		DEBUG("new BSD partition added");
 	}
 	table->type = PTABLE_BSD;
 out:
 	free(buf);
 	return (table);
 }
 
 #ifdef LOADER_VTOC8_SUPPORT
 static enum partition_type
 vtoc8_parttype(uint16_t type)
 {
 
 	switch (type) {
 	case VTOC_TAG_FREEBSD_NANDFS:
 		return (PART_FREEBSD_NANDFS);
 	case VTOC_TAG_FREEBSD_SWAP:
 		return (PART_FREEBSD_SWAP);
 	case VTOC_TAG_FREEBSD_UFS:
 		return (PART_FREEBSD_UFS);
 	case VTOC_TAG_FREEBSD_VINUM:
 		return (PART_FREEBSD_VINUM);
 	case VTOC_TAG_FREEBSD_ZFS:
 		return (PART_FREEBSD_ZFS);
 	}
 	return (PART_UNKNOWN);
 }
 
 static struct ptable*
 ptable_vtoc8read(struct ptable *table, void *dev, diskread_t dread)
 {
 	struct pentry *entry;
 	struct vtoc8 *dl;
 	u_char *buf;
 	uint16_t sum, heads, sectors;
 	int i;
 
 	if (table->sectorsize != sizeof(struct vtoc8))
 		return (table);
 	buf = malloc(table->sectorsize);
 	if (buf == NULL)
 		return (table);
 	if (dread(dev, buf, 1, 0) != 0) {
 		DEBUG("read failed");
 		ptable_close(table);
 		table = NULL;
 		goto out;
 	}
 	dl = (struct vtoc8 *)buf;
 	/* Check the sum */
 	for (i = sum = 0; i < sizeof(struct vtoc8); i += sizeof(sum))
 		sum ^= be16dec(buf + i);
 	if (sum != 0) {
 		DEBUG("incorrect checksum");
 		goto out;
 	}
 	if (be16toh(dl->nparts) != VTOC8_NPARTS) {
 		DEBUG("invalid number of entries");
 		goto out;
 	}
 	sectors = be16toh(dl->nsecs);
 	heads = be16toh(dl->nheads);
 	if (sectors * heads == 0) {
 		DEBUG("invalid geometry");
 		goto out;
 	}
 	DEBUG("VTOC8 detected");
 	for (i = 0; i < VTOC8_NPARTS; i++) {
 		dl->part[i].tag = be16toh(dl->part[i].tag);
 		if (i == VTOC_RAW_PART ||
 		    dl->part[i].tag == VTOC_TAG_UNASSIGNED)
 			continue;
 		entry = malloc(sizeof(*entry));
 		if (entry == NULL)
 			break;
 		entry->part.start = be32toh(dl->map[i].cyl) * heads * sectors;
 		entry->part.end = be32toh(dl->map[i].nblks) +
 		    entry->part.start - 1;
 		entry->part.type = vtoc8_parttype(dl->part[i].tag);
 		entry->part.index = i; /* starts from zero */
 		entry->type.vtoc8 = dl->part[i].tag;
 		STAILQ_INSERT_TAIL(&table->entries, entry, entry);
 		DEBUG("new VTOC8 partition added");
 	}
 	table->type = PTABLE_VTOC8;
 out:
 	free(buf);
 	return (table);
 
 }
 #endif /* LOADER_VTOC8_SUPPORT */
 
 struct ptable*
 ptable_open(void *dev, uint64_t sectors, uint16_t sectorsize,
     diskread_t *dread)
 {
 	struct dos_partition *dp;
 	struct ptable *table;
 	u_char *buf;
 	int i, count;
 #ifdef LOADER_MBR_SUPPORT
 	struct pentry *entry;
 	uint32_t start, end;
 	int has_ext;
 #endif
 	table = NULL;
 	buf = malloc(sectorsize);
 	if (buf == NULL)
 		return (NULL);
 	/* First, read the MBR. */
 	if (dread(dev, buf, 1, DOSBBSECTOR) != 0) {
 		DEBUG("read failed");
 		goto out;
 	}
 
 	table = malloc(sizeof(*table));
 	if (table == NULL)
 		goto out;
 	table->sectors = sectors;
 	table->sectorsize = sectorsize;
 	table->type = PTABLE_NONE;
 	STAILQ_INIT(&table->entries);
 
 #ifdef LOADER_VTOC8_SUPPORT
 	if (be16dec(buf + offsetof(struct vtoc8, magic)) == VTOC_MAGIC) {
 		if (ptable_vtoc8read(table, dev, dread) == NULL) {
 			/* Read error. */
 			table = NULL;
 			goto out;
 		} else if (table->type == PTABLE_VTOC8)
 			goto out;
 	}
 #endif
 	/* Check the BSD label. */
 	if (ptable_bsdread(table, dev, dread) == NULL) { /* Read error. */
 		table = NULL;
 		goto out;
 	} else if (table->type == PTABLE_BSD)
 		goto out;
 
 #if defined(LOADER_GPT_SUPPORT) || defined(LOADER_MBR_SUPPORT)
 	/* Check the MBR magic. */
 	if (buf[DOSMAGICOFFSET] != 0x55 ||
 	    buf[DOSMAGICOFFSET + 1] != 0xaa) {
 		DEBUG("magic sequence not found");
 #if defined(LOADER_GPT_SUPPORT)
 		/* There is no PMBR, check that we have backup GPT */
 		table->type = PTABLE_GPT;
 		table = ptable_gptread(table, dev, dread);
 #endif
 		goto out;
 	}
 	/* Check that we have PMBR. Also do some validation. */
 	dp = (struct dos_partition *)(buf + DOSPARTOFF);
 	for (i = 0, count = 0; i < NDOSPART; i++) {
 		if (dp[i].dp_flag != 0 && dp[i].dp_flag != 0x80) {
 			DEBUG("invalid partition flag %x", dp[i].dp_flag);
 			goto out;
 		}
 #ifdef LOADER_GPT_SUPPORT
 		if (dp[i].dp_typ == DOSPTYP_PMBR) {
 			table->type = PTABLE_GPT;
 			DEBUG("PMBR detected");
 		}
 #endif
 		if (dp[i].dp_typ != 0)
 			count++;
 	}
 	/* Do we have some invalid values? */
 	if (table->type == PTABLE_GPT && count > 1) {
 		if (dp[1].dp_typ != DOSPTYP_HFS) {
 			table->type = PTABLE_NONE;
 			DEBUG("Incorrect PMBR, ignore it");
 		} else
 			DEBUG("Bootcamp detected");
 	}
 #ifdef LOADER_GPT_SUPPORT
 	if (table->type == PTABLE_GPT) {
 		table = ptable_gptread(table, dev, dread);
 		goto out;
 	}
 #endif
 #ifdef LOADER_MBR_SUPPORT
 	/* Read MBR. */
 	DEBUG("MBR detected");
 	table->type = PTABLE_MBR;
 	for (i = has_ext = 0; i < NDOSPART; i++) {
 		if (dp[i].dp_typ == 0)
 			continue;
 		start = le32dec(&(dp[i].dp_start));
 		end = le32dec(&(dp[i].dp_size));
 		if (start == 0 || end == 0)
 			continue;
 #if 0	/* Some BIOSes return an incorrect number of sectors */
 		if (start + end - 1 >= sectors)
 			continue;	/* XXX: ignore */
 #endif
 		if (dp[i].dp_typ == DOSPTYP_EXT ||
 		    dp[i].dp_typ == DOSPTYP_EXTLBA)
 			has_ext = 1;
 		entry = malloc(sizeof(*entry));
 		if (entry == NULL)
 			break;
 		entry->part.start = start;
 		entry->part.end = start + end - 1;
 		entry->part.index = i + 1;
 		entry->part.type = mbr_parttype(dp[i].dp_typ);
 		entry->flags = dp[i].dp_flag;
 		entry->type.mbr = dp[i].dp_typ;
 		STAILQ_INSERT_TAIL(&table->entries, entry, entry);
 		DEBUG("new MBR partition added");
 	}
 	if (has_ext) {
 		table = ptable_ebrread(table, dev, dread);
 		/* FALLTHROUGH */
 	}
 #endif /* LOADER_MBR_SUPPORT */
 #endif /* LOADER_MBR_SUPPORT || LOADER_GPT_SUPPORT */
 out:
 	free(buf);
 	return (table);
 }
 
 void
 ptable_close(struct ptable *table)
 {
 	struct pentry *entry;
 
 	while (!STAILQ_EMPTY(&table->entries)) {
 		entry = STAILQ_FIRST(&table->entries);
 		STAILQ_REMOVE_HEAD(&table->entries, entry);
 		free(entry);
 	}
 	free(table);
 }
 
 enum ptable_type
 ptable_gettype(const struct ptable *table)
 {
 
 	return (table->type);
 }
 
+int
+ptable_getsize(const struct ptable *table, uint64_t *sizep)
+{
+	uint64_t tmp = table->sectors * table->sectorsize;
+
+	if (tmp < table->sectors)
+		return (EOVERFLOW);
+
+	if (sizep != NULL)
+		*sizep = tmp;
+	return (0);
+}
+
 int
 ptable_getpart(const struct ptable *table, struct ptable_entry *part, int index)
 {
 	struct pentry *entry;
 
 	if (part == NULL || table == NULL)
 		return (EINVAL);
 
 	STAILQ_FOREACH(entry, &table->entries, entry) {
 		if (entry->part.index != index)
 			continue;
 		memcpy(part, &entry->part, sizeof(*part));
 		return (0);
 	}
 	return (ENOENT);
 }
 
 /*
  * Search for a slice with the following preferences:
  *
  * 1: Active FreeBSD slice
  * 2: Non-active FreeBSD slice
  * 3: Active Linux slice
  * 4: non-active Linux slice
  * 5: Active FAT/FAT32 slice
  * 6: non-active FAT/FAT32 slice
  */
 #define PREF_RAWDISK	0
 #define PREF_FBSD_ACT	1
 #define PREF_FBSD	2
 #define PREF_LINUX_ACT	3
 #define PREF_LINUX	4
 #define PREF_DOS_ACT	5
 #define PREF_DOS	6
 #define PREF_NONE	7
 int
 ptable_getbestpart(const struct ptable *table, struct ptable_entry *part)
 {
 	struct pentry *entry, *best;
 	int pref, preflevel;
 
 	if (part == NULL || table == NULL)
 		return (EINVAL);
 
 	best = NULL;
 	preflevel = pref = PREF_NONE;
 	STAILQ_FOREACH(entry, &table->entries, entry) {
 #ifdef LOADER_MBR_SUPPORT
 		if (table->type == PTABLE_MBR) {
 			switch (entry->type.mbr) {
 			case DOSPTYP_386BSD:
 				pref = entry->flags & 0x80 ? PREF_FBSD_ACT:
 				    PREF_FBSD;
 				break;
 			case DOSPTYP_LINUX:
 				pref = entry->flags & 0x80 ? PREF_LINUX_ACT:
 				    PREF_LINUX;
 				break;
 			case 0x01:		/* DOS/Windows */
 			case 0x04:
 			case 0x06:
 			case 0x0c:
 			case 0x0e:
 			case DOSPTYP_FAT32:
 				pref = entry->flags & 0x80 ? PREF_DOS_ACT:
 				    PREF_DOS;
 				break;
 			default:
 				pref = PREF_NONE;
 			}
 		}
 #endif /* LOADER_MBR_SUPPORT */
 #ifdef LOADER_GPT_SUPPORT
 		if (table->type == PTABLE_GPT) {
 			if (entry->part.type == PART_DOS)
 				pref = PREF_DOS;
 			else if (entry->part.type == PART_FREEBSD_UFS ||
 			    entry->part.type == PART_FREEBSD_ZFS)
 				pref = PREF_FBSD;
 			else
 				pref = PREF_NONE;
 		}
 #endif /* LOADER_GPT_SUPPORT */
 		if (pref < preflevel) {
 			preflevel = pref;
 			best = entry;
 		}
 	}
 	if (best != NULL) {
 		memcpy(part, &best->part, sizeof(*part));
 		return (0);
 	}
 	return (ENOENT);
 }
 
 int
 ptable_iterate(const struct ptable *table, void *arg, ptable_iterate_t *iter)
 {
 	struct pentry *entry;
 	char name[32];
 	int ret = 0;
 
 	name[0] = '\0';
 	STAILQ_FOREACH(entry, &table->entries, entry) {
 #ifdef LOADER_MBR_SUPPORT
 		if (table->type == PTABLE_MBR)
 			sprintf(name, "s%d", entry->part.index);
 		else
 #endif
 #ifdef LOADER_GPT_SUPPORT
 		if (table->type == PTABLE_GPT)
 			sprintf(name, "p%d", entry->part.index);
 		else
 #endif
 #ifdef LOADER_VTOC8_SUPPORT
 		if (table->type == PTABLE_VTOC8)
 			sprintf(name, "%c", (u_char) 'a' +
 			    entry->part.index);
 		else
 #endif
 		if (table->type == PTABLE_BSD)
 			sprintf(name, "%c", (u_char) 'a' +
 			    entry->part.index);
 		if ((ret = iter(arg, name, &entry->part)) != 0)
 			return (ret);
 	}
 	return (ret);
 }
diff --git a/sys/boot/common/part.h b/sys/boot/common/part.h
index 11ca0632d10d..19bd6702fc69 100644
--- a/sys/boot/common/part.h
+++ b/sys/boot/common/part.h
@@ -1,82 +1,83 @@
 /*-
  * Copyright (c) 2012 Andrey V. Elsukov <ae@FreeBSD.org>
  * All rights reserved.
  *
  * 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.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``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 THE AUTHORS OR CONTRIBUTORS 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.
  *
  * $FreeBSD$
  */
 
 #ifndef _PART_H_
 #define	_PART_H_
 
 struct ptable;
 
 enum ptable_type {
 	PTABLE_NONE,
 	PTABLE_BSD,
 	PTABLE_MBR,
 	PTABLE_GPT,
 	PTABLE_VTOC8
 };
 
 enum partition_type {
 	PART_UNKNOWN,
 	PART_EFI,
 	PART_FREEBSD,
 	PART_FREEBSD_BOOT,
 	PART_FREEBSD_NANDFS,
 	PART_FREEBSD_UFS,
 	PART_FREEBSD_ZFS,
 	PART_FREEBSD_SWAP,
 	PART_FREEBSD_VINUM,
 	PART_LINUX,
 	PART_LINUX_SWAP,
 	PART_DOS,
 };
 
 struct ptable_entry {
 	uint64_t		start;
 	uint64_t		end;
 	int			index;
 	enum partition_type	type;
 };
 
 /* The offset and size are in sectors */
 typedef int (diskread_t)(void *arg, void *buf, size_t blocks, uint64_t offset);
 typedef int (ptable_iterate_t)(void *arg, const char *partname,
     const struct ptable_entry *part);
 
 struct ptable *ptable_open(void *dev, uint64_t sectors, uint16_t sectorsize,
     diskread_t *dread);
 void ptable_close(struct ptable *table);
 enum ptable_type ptable_gettype(const struct ptable *table);
+int ptable_getsize(const struct ptable *table, uint64_t *sizep);
 
 int ptable_getpart(const struct ptable *table, struct ptable_entry *part,
     int index);
 int ptable_getbestpart(const struct ptable *table, struct ptable_entry *part);
 
 int ptable_iterate(const struct ptable *table, void *arg,
     ptable_iterate_t *iter);
 const char *parttype2str(enum partition_type type);
 
 #endif	/* !_PART_H_ */
diff --git a/sys/boot/usb/storage/umass_loader.c b/sys/boot/usb/storage/umass_loader.c
index d8c09dd36f32..4c9711c2694b 100644
--- a/sys/boot/usb/storage/umass_loader.c
+++ b/sys/boot/usb/storage/umass_loader.c
@@ -1,229 +1,239 @@
 /* $FreeBSD$ */
 /*-
  * Copyright (c) 2014 Hans Petter Selasky <hselasky@FreeBSD.org>
  * All rights reserved.
  *
  * This software was developed by SRI International and the University of
  * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237)
  * ("CTSRD"), as part of the DARPA CRASH research programme.
  *
  * 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.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 THE AUTHOR OR CONTRIBUTORS 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.
  */
 
 #include <sys/param.h>
 
 #include <bootstrap.h>
 #include <stdarg.h>
 
 #include <stand.h>
 #include <disk.h>
 
 #define	HAVE_STANDARD_DEFS
 
 #include USB_GLOBAL_INCLUDE_FILE
 
 #include "umass_common.h"
 
 static int umass_disk_init(void);
 static int umass_disk_open(struct open_file *,...);
 static int umass_disk_close(struct open_file *);
 static void umass_disk_cleanup(void);
 static int umass_disk_ioctl(struct open_file *, u_long, void *);
 static int umass_disk_strategy(void *, int, daddr_t, size_t, char *, size_t *);
 static int umass_disk_print(int);
 
 struct devsw umass_disk = {
 	.dv_name = "umass",
 	.dv_type = DEVT_DISK,
 	.dv_init = umass_disk_init,
 	.dv_strategy = umass_disk_strategy,
 	.dv_open = umass_disk_open,
 	.dv_close = umass_disk_close,
 	.dv_ioctl = umass_disk_ioctl,
 	.dv_print = umass_disk_print,
 	.dv_cleanup = umass_disk_cleanup,
 };
 
 static int
 umass_disk_init(void)
 {
 	uint32_t time;
 
 	usb_init();
 	usb_needs_explore_all();
 
 	/* wait 8 seconds for a USB mass storage device to appear */
 	for (time = 0; time < (8 * hz); time++) {
 		usb_idle();
 		delay(1000000 / hz);
 		time++;
 		callout_process(1);
 		if (umass_uaa.device != NULL)
 			return (0);
 	}
 	return (0);
 }
 
 static int
 umass_disk_strategy(void *devdata, int flag, daddr_t dblk, size_t size,
     char *buf, size_t *rsizep)
 {
 	if (umass_uaa.device == NULL)
 		return (ENXIO);
 	if (rsizep != NULL)
 		*rsizep = 0;
 
 	if (flag == F_WRITE) {
 		if (usb_msc_write_10(umass_uaa.device, 0, dblk, size >> 9, buf) != 0)
 			return (EINVAL);
 	} else if (flag == F_READ) {
 		if (usb_msc_read_10(umass_uaa.device, 0, dblk, size >> 9, buf) != 0)
 			return (EINVAL);
 	} else {
 		return (EROFS);
 	}
 
 	if (rsizep != NULL)
 		*rsizep = size;
 	return (0);
 }
 
 static int
 umass_disk_open_sub(struct disk_devdesc *dev)
 {
 	uint32_t nblock;
 	uint32_t blocksize;
 
 	if (usb_msc_read_capacity(umass_uaa.device, 0, &nblock, &blocksize) != 0)
 		return (EINVAL);
 
 	return (disk_open(dev, ((uint64_t)nblock + 1) * (uint64_t)blocksize, blocksize, 0));
 }
 
 static int
 umass_disk_open(struct open_file *f,...)
 {
 	va_list ap;
 	struct disk_devdesc *dev;
 
 	va_start(ap, f);
 	dev = va_arg(ap, struct disk_devdesc *);
 	va_end(ap);
 
 	if (umass_uaa.device == NULL)
 		return (ENXIO);
 	if (dev->d_unit != 0)
 		return (EIO);
 	return (umass_disk_open_sub(dev));
 }
 
 static int
-umass_disk_ioctl(struct open_file *f __unused, u_long cmd, void *buf)
+umass_disk_ioctl(struct open_file *f, u_long cmd, void *buf)
 {
+	struct disk_devdesc *dev;
 	uint32_t nblock;
 	uint32_t blocksize;
+	int rc;
+
+	dev = (struct disk_devdesc *)(f->f_devdata);
+	if (dev == NULL)
+		return (EINVAL);
+
+	rc = disk_ioctl(dev, cmd, buf);
+	if (rc != ENOTTY)
+		return (rc);
 
 	switch (cmd) {
 	case DIOCGSECTORSIZE:
 	case DIOCGMEDIASIZE:
 		if (usb_msc_read_capacity(umass_uaa.device, 0,
 		    &nblock, &blocksize) != 0)
 			return (EINVAL);
 
 		if (cmd == DIOCGMEDIASIZE)
 			*(uint64_t*)buf = nblock;
 		else
 			*(uint32_t*)buf = blocksize;
 
 		return (0);
 	default:
 		return (ENXIO);
 	}
 }
 
 static int
 umass_disk_close(struct open_file *f)
 {
 	struct disk_devdesc *dev;
 
 	dev = (struct disk_devdesc *)f->f_devdata;
 	return (disk_close(dev));
 }
 
 static int
 umass_disk_print(int verbose)
 {
 	struct disk_devdesc dev;
 
 	printf("%s devices:", umass_disk.dv_name);
 	if (pager_output("\n") != 0)
 		return (1);
 
 	memset(&dev, 0, sizeof(dev));
 
 	ret = pager_output("    umass0   UMASS device\n");
 	if (ret != 0)
 		return (ret);
 	dev.d_dev = &umass_disk;
 	dev.d_unit = 0;
 	dev.d_slice = -1;
 	dev.d_partition = -1;
 
 	if (umass_disk_open_sub(&dev) == 0) {
 		ret = disk_print(&dev, "    umass0", verbose);
 		disk_close(&dev);
 	}
 	return (ret);
 }
 
 static void
 umass_disk_cleanup(void)
 {
 	disk_cleanup(&umass_disk);
 
 	usb_uninit();
 }
 
 
 /* USB specific functions */
 
 extern void callout_process(int);
 extern void usb_idle(void);
 extern void usb_init(void);
 extern void usb_uninit(void);
 
 void
 DELAY(unsigned int usdelay)
 {
 	delay(usdelay);
 }
 
 int
 pause(const char *what, int timeout)
 {
 	if (timeout == 0)
 		timeout = 1;
 
 	delay((1000000 / hz) * timeout);
 
 	return (0);
 }
diff --git a/sys/boot/zfs/zfs.c b/sys/boot/zfs/zfs.c
index 78823a5f53f5..cff127ff2bb3 100644
--- a/sys/boot/zfs/zfs.c
+++ b/sys/boot/zfs/zfs.c
@@ -1,907 +1,907 @@
 /*-
  * Copyright (c) 2007 Doug Rabson
  * All rights reserved.
  *
  * 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.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 THE AUTHOR OR CONTRIBUTORS 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.
  *
  *	$FreeBSD$
  */
 
 #include <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
 /*
  *	Stand-alone file reading package.
  */
 
 #include <sys/disk.h>
 #include <sys/param.h>
 #include <sys/time.h>
 #include <sys/queue.h>
 #include <part.h>
 #include <stddef.h>
 #include <stdarg.h>
 #include <string.h>
 #include <stand.h>
 #include <bootstrap.h>
 
 #include "libzfs.h"
 
 #include "zfsimpl.c"
 
 /* Define the range of indexes to be populated with ZFS Boot Environments */
 #define		ZFS_BE_FIRST	4
 #define		ZFS_BE_LAST	8
 
 static int	zfs_open(const char *path, struct open_file *f);
 static int	zfs_write(struct open_file *f, void *buf, size_t size, size_t *resid);
 static int	zfs_close(struct open_file *f);
 static int	zfs_read(struct open_file *f, void *buf, size_t size, size_t *resid);
 static off_t	zfs_seek(struct open_file *f, off_t offset, int where);
 static int	zfs_stat(struct open_file *f, struct stat *sb);
 static int	zfs_readdir(struct open_file *f, struct dirent *d);
 
 struct devsw zfs_dev;
 
 struct fs_ops zfs_fsops = {
 	"zfs",
 	zfs_open,
 	zfs_close,
 	zfs_read,
 	zfs_write,
 	zfs_seek,
 	zfs_stat,
 	zfs_readdir
 };
 
 /*
  * In-core open file.
  */
 struct file {
 	off_t		f_seekp;	/* seek pointer */
 	dnode_phys_t	f_dnode;
 	uint64_t	f_zap_type;	/* zap type for readdir */
 	uint64_t	f_num_leafs;	/* number of fzap leaf blocks */
 	zap_leaf_phys_t	*f_zap_leaf;	/* zap leaf buffer */
 };
 
 static int	zfs_env_index;
 static int	zfs_env_count;
 
 SLIST_HEAD(zfs_be_list, zfs_be_entry) zfs_be_head = SLIST_HEAD_INITIALIZER(zfs_be_head);
 struct zfs_be_list *zfs_be_headp;
 struct zfs_be_entry {
 	const char *name;
 	SLIST_ENTRY(zfs_be_entry) entries;
 } *zfs_be, *zfs_be_tmp;
 
 /*
  * Open a file.
  */
 static int
 zfs_open(const char *upath, struct open_file *f)
 {
 	struct zfsmount *mount = (struct zfsmount *)f->f_devdata;
 	struct file *fp;
 	int rc;
 
 	if (f->f_dev != &zfs_dev)
 		return (EINVAL);
 
 	/* allocate file system specific data structure */
 	fp = malloc(sizeof(struct file));
 	bzero(fp, sizeof(struct file));
 	f->f_fsdata = (void *)fp;
 
 	rc = zfs_lookup(mount, upath, &fp->f_dnode);
 	fp->f_seekp = 0;
 	if (rc) {
 		f->f_fsdata = NULL;
 		free(fp);
 	}
 	return (rc);
 }
 
 static int
 zfs_close(struct open_file *f)
 {
 	struct file *fp = (struct file *)f->f_fsdata;
 
 	dnode_cache_obj = 0;
 	f->f_fsdata = (void *)0;
 	if (fp == (struct file *)0)
 		return (0);
 
 	free(fp);
 	return (0);
 }
 
 /*
  * Copy a portion of a file into kernel memory.
  * Cross block boundaries when necessary.
  */
 static int
 zfs_read(struct open_file *f, void *start, size_t size, size_t *resid	/* out */)
 {
 	const spa_t *spa = ((struct zfsmount *)f->f_devdata)->spa;
 	struct file *fp = (struct file *)f->f_fsdata;
 	struct stat sb;
 	size_t n;
 	int rc;
 
 	rc = zfs_stat(f, &sb);
 	if (rc)
 		return (rc);
 	n = size;
 	if (fp->f_seekp + n > sb.st_size)
 		n = sb.st_size - fp->f_seekp;
 
 	rc = dnode_read(spa, &fp->f_dnode, fp->f_seekp, start, n);
 	if (rc)
 		return (rc);
 
 	if (0) {
 	    int i;
 	    for (i = 0; i < n; i++)
 		putchar(((char*) start)[i]);
 	}
 	fp->f_seekp += n;
 	if (resid)
 		*resid = size - n;
 
 	return (0);
 }
 
 /*
  * Don't be silly - the bootstrap has no business writing anything.
  */
 static int
 zfs_write(struct open_file *f, void *start, size_t size, size_t *resid	/* out */)
 {
 
 	return (EROFS);
 }
 
 static off_t
 zfs_seek(struct open_file *f, off_t offset, int where)
 {
 	struct file *fp = (struct file *)f->f_fsdata;
 
 	switch (where) {
 	case SEEK_SET:
 		fp->f_seekp = offset;
 		break;
 	case SEEK_CUR:
 		fp->f_seekp += offset;
 		break;
 	case SEEK_END:
 	    {
 		struct stat sb;
 		int error;
 
 		error = zfs_stat(f, &sb);
 		if (error != 0) {
 			errno = error;
 			return (-1);
 		}
 		fp->f_seekp = sb.st_size - offset;
 		break;
 	    }
 	default:
 		errno = EINVAL;
 		return (-1);
 	}
 	return (fp->f_seekp);
 }
 
 static int
 zfs_stat(struct open_file *f, struct stat *sb)
 {
 	const spa_t *spa = ((struct zfsmount *)f->f_devdata)->spa;
 	struct file *fp = (struct file *)f->f_fsdata;
 
 	return (zfs_dnode_stat(spa, &fp->f_dnode, sb));
 }
 
 static int
 zfs_readdir(struct open_file *f, struct dirent *d)
 {
 	const spa_t *spa = ((struct zfsmount *)f->f_devdata)->spa;
 	struct file *fp = (struct file *)f->f_fsdata;
 	mzap_ent_phys_t mze;
 	struct stat sb;
 	size_t bsize = fp->f_dnode.dn_datablkszsec << SPA_MINBLOCKSHIFT;
 	int rc;
 
 	rc = zfs_stat(f, &sb);
 	if (rc)
 		return (rc);
 	if (!S_ISDIR(sb.st_mode))
 		return (ENOTDIR);
 
 	/*
 	 * If this is the first read, get the zap type.
 	 */
 	if (fp->f_seekp == 0) {
 		rc = dnode_read(spa, &fp->f_dnode,
 				0, &fp->f_zap_type, sizeof(fp->f_zap_type));
 		if (rc)
 			return (rc);
 
 		if (fp->f_zap_type == ZBT_MICRO) {
 			fp->f_seekp = offsetof(mzap_phys_t, mz_chunk);
 		} else {
 			rc = dnode_read(spa, &fp->f_dnode,
 					offsetof(zap_phys_t, zap_num_leafs),
 					&fp->f_num_leafs,
 					sizeof(fp->f_num_leafs));
 			if (rc)
 				return (rc);
 
 			fp->f_seekp = bsize;
 			fp->f_zap_leaf = (zap_leaf_phys_t *)malloc(bsize);
 			rc = dnode_read(spa, &fp->f_dnode,
 					fp->f_seekp,
 					fp->f_zap_leaf,
 					bsize);
 			if (rc)
 				return (rc);
 		}
 	}
 
 	if (fp->f_zap_type == ZBT_MICRO) {
 	mzap_next:
 		if (fp->f_seekp >= bsize)
 			return (ENOENT);
 
 		rc = dnode_read(spa, &fp->f_dnode,
 				fp->f_seekp, &mze, sizeof(mze));
 		if (rc)
 			return (rc);
 		fp->f_seekp += sizeof(mze);
 
 		if (!mze.mze_name[0])
 			goto mzap_next;
 
 		d->d_fileno = ZFS_DIRENT_OBJ(mze.mze_value);
 		d->d_type = ZFS_DIRENT_TYPE(mze.mze_value);
 		strcpy(d->d_name, mze.mze_name);
 		d->d_namlen = strlen(d->d_name);
 		return (0);
 	} else {
 		zap_leaf_t zl;
 		zap_leaf_chunk_t *zc, *nc;
 		int chunk;
 		size_t namelen;
 		char *p;
 		uint64_t value;
 
 		/*
 		 * Initialise this so we can use the ZAP size
 		 * calculating macros.
 		 */
 		zl.l_bs = ilog2(bsize);
 		zl.l_phys = fp->f_zap_leaf;
 
 		/*
 		 * Figure out which chunk we are currently looking at
 		 * and consider seeking to the next leaf. We use the
 		 * low bits of f_seekp as a simple chunk index.
 		 */
 	fzap_next:
 		chunk = fp->f_seekp & (bsize - 1);
 		if (chunk == ZAP_LEAF_NUMCHUNKS(&zl)) {
 			fp->f_seekp = rounddown2(fp->f_seekp, bsize) + bsize;
 			chunk = 0;
 
 			/*
 			 * Check for EOF and read the new leaf.
 			 */
 			if (fp->f_seekp >= bsize * fp->f_num_leafs)
 				return (ENOENT);
 
 			rc = dnode_read(spa, &fp->f_dnode,
 					fp->f_seekp,
 					fp->f_zap_leaf,
 					bsize);
 			if (rc)
 				return (rc);
 		}
 
 		zc = &ZAP_LEAF_CHUNK(&zl, chunk);
 		fp->f_seekp++;
 		if (zc->l_entry.le_type != ZAP_CHUNK_ENTRY)
 			goto fzap_next;
 
 		namelen = zc->l_entry.le_name_numints;
 		if (namelen > sizeof(d->d_name))
 			namelen = sizeof(d->d_name);
 
 		/*
 		 * Paste the name back together.
 		 */
 		nc = &ZAP_LEAF_CHUNK(&zl, zc->l_entry.le_name_chunk);
 		p = d->d_name;
 		while (namelen > 0) {
 			int len;
 			len = namelen;
 			if (len > ZAP_LEAF_ARRAY_BYTES)
 				len = ZAP_LEAF_ARRAY_BYTES;
 			memcpy(p, nc->l_array.la_array, len);
 			p += len;
 			namelen -= len;
 			nc = &ZAP_LEAF_CHUNK(&zl, nc->l_array.la_next);
 		}
 		d->d_name[sizeof(d->d_name) - 1] = 0;
 
 		/*
 		 * Assume the first eight bytes of the value are
 		 * a uint64_t.
 		 */
 		value = fzap_leaf_value(&zl, zc);
 
 		d->d_fileno = ZFS_DIRENT_OBJ(value);
 		d->d_type = ZFS_DIRENT_TYPE(value);
 		d->d_namlen = strlen(d->d_name);
 
 		return (0);
 	}
 }
 
 static int
 vdev_read(vdev_t *vdev, void *priv, off_t offset, void *buf, size_t size)
 {
 	int fd;
 
 	fd = (uintptr_t) priv;
 	lseek(fd, offset, SEEK_SET);
 	if (read(fd, buf, size) == size) {
 		return 0;
 	} else {
 		return (EIO);
 	}
 }
 
 static int
 zfs_dev_init(void)
 {
 	spa_t *spa;
 	spa_t *next;
 	spa_t *prev;
 
 	zfs_init();
 	if (archsw.arch_zfs_probe == NULL)
 		return (ENXIO);
 	archsw.arch_zfs_probe();
 
 	prev = NULL;
 	spa = STAILQ_FIRST(&zfs_pools);
 	while (spa != NULL) {
 		next = STAILQ_NEXT(spa, spa_link);
 		if (zfs_spa_init(spa)) {
 			if (prev == NULL)
 				STAILQ_REMOVE_HEAD(&zfs_pools, spa_link);
 			else
 				STAILQ_REMOVE_AFTER(&zfs_pools, prev, spa_link);
 		} else
 			prev = spa;
 		spa = next;
 	}
 	return (0);
 }
 
 struct zfs_probe_args {
 	int		fd;
 	const char	*devname;
 	uint64_t	*pool_guid;
 	u_int		secsz;
 };
 
 static int
 zfs_diskread(void *arg, void *buf, size_t blocks, uint64_t offset)
 {
 	struct zfs_probe_args *ppa;
 
 	ppa = (struct zfs_probe_args *)arg;
 	return (vdev_read(NULL, (void *)(uintptr_t)ppa->fd,
 	    offset * ppa->secsz, buf, blocks * ppa->secsz));
 }
 
 static int
 zfs_probe(int fd, uint64_t *pool_guid)
 {
 	spa_t *spa;
 	int ret;
 
 	ret = vdev_probe(vdev_read, (void *)(uintptr_t)fd, &spa);
 	if (ret == 0 && pool_guid != NULL)
 		*pool_guid = spa->spa_guid;
 	return (ret);
 }
 
 static int
 zfs_probe_partition(void *arg, const char *partname,
     const struct ptable_entry *part)
 {
 	struct zfs_probe_args *ppa, pa;
 	struct ptable *table;
 	char devname[32];
 	int ret;
 
 	/* Probe only freebsd-zfs and freebsd partitions */
 	if (part->type != PART_FREEBSD &&
 	    part->type != PART_FREEBSD_ZFS)
 		return (0);
 
 	ppa = (struct zfs_probe_args *)arg;
 	strncpy(devname, ppa->devname, strlen(ppa->devname) - 1);
 	devname[strlen(ppa->devname) - 1] = '\0';
 	sprintf(devname, "%s%s:", devname, partname);
 	pa.fd = open(devname, O_RDONLY);
 	if (pa.fd == -1)
 		return (0);
 	ret = zfs_probe(pa.fd, ppa->pool_guid);
 	if (ret == 0)
 		return (0);
 	/* Do we have BSD label here? */
 	if (part->type == PART_FREEBSD) {
 		pa.devname = devname;
 		pa.pool_guid = ppa->pool_guid;
 		pa.secsz = ppa->secsz;
 		table = ptable_open(&pa, part->end - part->start + 1,
 		    ppa->secsz, zfs_diskread);
 		if (table != NULL) {
 			ptable_iterate(table, &pa, zfs_probe_partition);
 			ptable_close(table);
 		}
 	}
 	close(pa.fd);
 	return (0);
 }
 
 int
 zfs_probe_dev(const char *devname, uint64_t *pool_guid)
 {
 	struct ptable *table;
 	struct zfs_probe_args pa;
-	off_t mediasz;
+	uint64_t mediasz;
 	int ret;
 
 	pa.fd = open(devname, O_RDONLY);
 	if (pa.fd == -1)
 		return (ENXIO);
 	/* Probe the whole disk */
 	ret = zfs_probe(pa.fd, pool_guid);
 	if (ret == 0)
 		return (0);
 	/* Probe each partition */
 	ret = ioctl(pa.fd, DIOCGMEDIASIZE, &mediasz);
 	if (ret == 0)
 		ret = ioctl(pa.fd, DIOCGSECTORSIZE, &pa.secsz);
 	if (ret == 0) {
 		pa.devname = devname;
 		pa.pool_guid = pool_guid;
 		table = ptable_open(&pa, mediasz / pa.secsz, pa.secsz,
 		    zfs_diskread);
 		if (table != NULL) {
 			ptable_iterate(table, &pa, zfs_probe_partition);
 			ptable_close(table);
 		}
 	}
 	close(pa.fd);
 	return (ret);
 }
 
 /*
  * Print information about ZFS pools
  */
 static int
 zfs_dev_print(int verbose)
 {
 	spa_t *spa;
 	char line[80];
 	int ret = 0;
 
 	if (STAILQ_EMPTY(&zfs_pools))
 		return (0);
 
 	printf("%s devices:", zfs_dev.dv_name);
 	if ((ret = pager_output("\n")) != 0)
 		return (ret);
 
 	if (verbose) {
 		return (spa_all_status());
 	}
 	STAILQ_FOREACH(spa, &zfs_pools, spa_link) {
 		snprintf(line, sizeof(line), "    zfs:%s\n", spa->spa_name);
 		ret = pager_output(line);
 		if (ret != 0)
 			break;
 	}
 	return (ret);
 }
 
 /*
  * Attempt to open the pool described by (dev) for use by (f).
  */
 static int
 zfs_dev_open(struct open_file *f, ...)
 {
 	va_list		args;
 	struct zfs_devdesc	*dev;
 	struct zfsmount	*mount;
 	spa_t		*spa;
 	int		rv;
 
 	va_start(args, f);
 	dev = va_arg(args, struct zfs_devdesc *);
 	va_end(args);
 
 	if (dev->pool_guid == 0)
 		spa = STAILQ_FIRST(&zfs_pools);
 	else
 		spa = spa_find_by_guid(dev->pool_guid);
 	if (!spa)
 		return (ENXIO);
 	mount = malloc(sizeof(*mount));
 	rv = zfs_mount(spa, dev->root_guid, mount);
 	if (rv != 0) {
 		free(mount);
 		return (rv);
 	}
 	if (mount->objset.os_type != DMU_OST_ZFS) {
 		printf("Unexpected object set type %ju\n",
 		    (uintmax_t)mount->objset.os_type);
 		free(mount);
 		return (EIO);
 	}
 	f->f_devdata = mount;
 	free(dev);
 	return (0);
 }
 
 static int
 zfs_dev_close(struct open_file *f)
 {
 
 	free(f->f_devdata);
 	f->f_devdata = NULL;
 	return (0);
 }
 
 static int
 zfs_dev_strategy(void *devdata, int rw, daddr_t dblk, size_t size, char *buf, size_t *rsize)
 {
 
 	return (ENOSYS);
 }
 
 struct devsw zfs_dev = {
 	.dv_name = "zfs",
 	.dv_type = DEVT_ZFS,
 	.dv_init = zfs_dev_init,
 	.dv_strategy = zfs_dev_strategy,
 	.dv_open = zfs_dev_open,
 	.dv_close = zfs_dev_close,
 	.dv_ioctl = noioctl,
 	.dv_print = zfs_dev_print,
 	.dv_cleanup = NULL
 };
 
 int
 zfs_parsedev(struct zfs_devdesc *dev, const char *devspec, const char **path)
 {
 	static char	rootname[ZFS_MAXNAMELEN];
 	static char	poolname[ZFS_MAXNAMELEN];
 	spa_t		*spa;
 	const char	*end;
 	const char	*np;
 	const char	*sep;
 	int		rv;
 
 	np = devspec;
 	if (*np != ':')
 		return (EINVAL);
 	np++;
 	end = strchr(np, ':');
 	if (end == NULL)
 		return (EINVAL);
 	sep = strchr(np, '/');
 	if (sep == NULL || sep >= end)
 		sep = end;
 	memcpy(poolname, np, sep - np);
 	poolname[sep - np] = '\0';
 	if (sep < end) {
 		sep++;
 		memcpy(rootname, sep, end - sep);
 		rootname[end - sep] = '\0';
 	}
 	else
 		rootname[0] = '\0';
 
 	spa = spa_find_by_name(poolname);
 	if (!spa)
 		return (ENXIO);
 	dev->pool_guid = spa->spa_guid;
 	rv = zfs_lookup_dataset(spa, rootname, &dev->root_guid);
 	if (rv != 0)
 		return (rv);
 	if (path != NULL)
 		*path = (*end == '\0') ? end : end + 1;
 	dev->d_dev = &zfs_dev;
 	dev->d_type = zfs_dev.dv_type;
 	return (0);
 }
 
 char *
 zfs_fmtdev(void *vdev)
 {
 	static char		rootname[ZFS_MAXNAMELEN];
 	static char		buf[2 * ZFS_MAXNAMELEN + 8];
 	struct zfs_devdesc	*dev = (struct zfs_devdesc *)vdev;
 	spa_t			*spa;
 
 	buf[0] = '\0';
 	if (dev->d_type != DEVT_ZFS)
 		return (buf);
 
 	if (dev->pool_guid == 0) {
 		spa = STAILQ_FIRST(&zfs_pools);
 		dev->pool_guid = spa->spa_guid;
 	} else
 		spa = spa_find_by_guid(dev->pool_guid);
 	if (spa == NULL) {
 		printf("ZFS: can't find pool by guid\n");
 		return (buf);
 	}
 	if (dev->root_guid == 0 && zfs_get_root(spa, &dev->root_guid)) {
 		printf("ZFS: can't find root filesystem\n");
 		return (buf);
 	}
 	if (zfs_rlookup(spa, dev->root_guid, rootname)) {
 		printf("ZFS: can't find filesystem by guid\n");
 		return (buf);
 	}
 
 	if (rootname[0] == '\0')
 		sprintf(buf, "%s:%s:", dev->d_dev->dv_name, spa->spa_name);
 	else
 		sprintf(buf, "%s:%s/%s:", dev->d_dev->dv_name, spa->spa_name,
 		    rootname);
 	return (buf);
 }
 
 int
 zfs_list(const char *name)
 {
 	static char	poolname[ZFS_MAXNAMELEN];
 	uint64_t	objid;
 	spa_t		*spa;
 	const char	*dsname;
 	int		len;
 	int		rv;
 
 	len = strlen(name);
 	dsname = strchr(name, '/');
 	if (dsname != NULL) {
 		len = dsname - name;
 		dsname++;
 	} else
 		dsname = "";
 	memcpy(poolname, name, len);
 	poolname[len] = '\0';
 
 	spa = spa_find_by_name(poolname);
 	if (!spa)
 		return (ENXIO);
 	rv = zfs_lookup_dataset(spa, dsname, &objid);
 	if (rv != 0)
 		return (rv);
 
 	return (zfs_list_dataset(spa, objid));
 }
 
 void
 init_zfs_bootenv(char *currdev)
 {
 	char *beroot;
 
 	if (strlen(currdev) == 0)
 		return;
 	if(strncmp(currdev, "zfs:", 4) != 0)
 		return;
 	/* Remove the trailing : */
 	currdev[strlen(currdev) - 1] = '\0';
 	setenv("zfs_be_active", currdev, 1);
 	setenv("zfs_be_currpage", "1", 1);
 	/* Forward past zfs: */
 	currdev = strchr(currdev, ':');
 	currdev++;
 	/* Remove the last element (current bootenv) */
 	beroot = strrchr(currdev, '/');
 	if (beroot != NULL)
 		beroot[0] = '\0';
 	beroot = currdev;
 	setenv("zfs_be_root", beroot, 1);
 }
 
 int
 zfs_bootenv(const char *name)
 {
 	static char	poolname[ZFS_MAXNAMELEN], *dsname, *root;
 	char		becount[4];
 	uint64_t	objid;
 	spa_t		*spa;
 	int		len, rv, pages, perpage, currpage;
 
 	if (name == NULL)
 		return (EINVAL);
 	if ((root = getenv("zfs_be_root")) == NULL)
 		return (EINVAL);
 
 	if (strcmp(name, root) != 0) {
 		if (setenv("zfs_be_root", name, 1) != 0)
 			return (ENOMEM);
 	}
 
 	SLIST_INIT(&zfs_be_head);
 	zfs_env_count = 0;
 	len = strlen(name);
 	dsname = strchr(name, '/');
 	if (dsname != NULL) {
 		len = dsname - name;
 		dsname++;
 	} else
 		dsname = "";
 	memcpy(poolname, name, len);
 	poolname[len] = '\0';
 
 	spa = spa_find_by_name(poolname);
 	if (!spa)
 		return (ENXIO);
 	rv = zfs_lookup_dataset(spa, dsname, &objid);
 	if (rv != 0)
 		return (rv);
 	rv = zfs_callback_dataset(spa, objid, zfs_belist_add);
 
 	/* Calculate and store the number of pages of BEs */
 	perpage = (ZFS_BE_LAST - ZFS_BE_FIRST + 1);
 	pages = (zfs_env_count / perpage) + ((zfs_env_count % perpage) > 0 ? 1 : 0);
 	snprintf(becount, 4, "%d", pages);
 	if (setenv("zfs_be_pages", becount, 1) != 0)
 		return (ENOMEM);
 
 	/* Roll over the page counter if it has exceeded the maximum */
 	currpage = strtol(getenv("zfs_be_currpage"), NULL, 10);
 	if (currpage > pages) {
 		if (setenv("zfs_be_currpage", "1", 1) != 0)
 			return (ENOMEM);
 	}
 
 	/* Populate the menu environment variables */
 	zfs_set_env();
 
 	/* Clean up the SLIST of ZFS BEs */
 	while (!SLIST_EMPTY(&zfs_be_head)) {
 		zfs_be = SLIST_FIRST(&zfs_be_head);
 		SLIST_REMOVE_HEAD(&zfs_be_head, entries);
 		free(zfs_be);
 	}
 
 	return (rv);
 }
 
 int
 zfs_belist_add(const char *name, uint64_t value __unused)
 {
 
 	/* Skip special datasets that start with a $ character */
 	if (strncmp(name, "$", 1) == 0) {
 		return (0);
 	}
 	/* Add the boot environment to the head of the SLIST */
 	zfs_be = malloc(sizeof(struct zfs_be_entry));
 	if (zfs_be == NULL) {
 		return (ENOMEM);
 	}
 	zfs_be->name = name;
 	SLIST_INSERT_HEAD(&zfs_be_head, zfs_be, entries);
 	zfs_env_count++;
 
 	return (0);
 }
 
 int
 zfs_set_env(void)
 {
 	char envname[32], envval[256];
 	char *beroot, *pagenum;
 	int rv, page, ctr;
 
 	beroot = getenv("zfs_be_root");
 	if (beroot == NULL) {
 		return (1);
 	}
 
 	pagenum = getenv("zfs_be_currpage");
 	if (pagenum != NULL) {
 		page = strtol(pagenum, NULL, 10);
 	} else {
 		page = 1;
 	}
 
 	ctr = 1;
 	rv = 0;
 	zfs_env_index = ZFS_BE_FIRST;
 	SLIST_FOREACH_SAFE(zfs_be, &zfs_be_head, entries, zfs_be_tmp) {
 		/* Skip to the requested page number */
 		if (ctr <= ((ZFS_BE_LAST - ZFS_BE_FIRST + 1) * (page - 1))) {
 			ctr++;
 			continue;
 		}
 		
 		snprintf(envname, sizeof(envname), "bootenvmenu_caption[%d]", zfs_env_index);
 		snprintf(envval, sizeof(envval), "%s", zfs_be->name);
 		rv = setenv(envname, envval, 1);
 		if (rv != 0) {
 			break;
 		}
 
 		snprintf(envname, sizeof(envname), "bootenvansi_caption[%d]", zfs_env_index);
 		rv = setenv(envname, envval, 1);
 		if (rv != 0){
 			break;
 		}
 
 		snprintf(envname, sizeof(envname), "bootenvmenu_command[%d]", zfs_env_index);
 		rv = setenv(envname, "set_bootenv", 1);
 		if (rv != 0){
 			break;
 		}
 
 		snprintf(envname, sizeof(envname), "bootenv_root[%d]", zfs_env_index);
 		snprintf(envval, sizeof(envval), "zfs:%s/%s", beroot, zfs_be->name);
 		rv = setenv(envname, envval, 1);
 		if (rv != 0){
 			break;
 		}
 
 		zfs_env_index++;
 		if (zfs_env_index > ZFS_BE_LAST) {
 			break;
 		}
 
 	}
 	
 	for (; zfs_env_index <= ZFS_BE_LAST; zfs_env_index++) {
 		snprintf(envname, sizeof(envname), "bootenvmenu_caption[%d]", zfs_env_index);
 		(void)unsetenv(envname);
 		snprintf(envname, sizeof(envname), "bootenvansi_caption[%d]", zfs_env_index);
 		(void)unsetenv(envname);
 		snprintf(envname, sizeof(envname), "bootenvmenu_command[%d]", zfs_env_index);
 		(void)unsetenv(envname);
 		snprintf(envname, sizeof(envname), "bootenv_root[%d]", zfs_env_index);
 		(void)unsetenv(envname);
 	}
 
 	return (rv);
 }