/*	$NetBSD: disksubr.c,v 1.58 2019/04/03 22:10:50 christos Exp $	*/

/*
 * Copyright (c) 1982, 1986, 1988 Regents of the University of California.
 * 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.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
 *
 *	@(#)ufs_disksubr.c	7.16 (Berkeley) 5/4/91
 */
/*-
 * Copyright (C) 1993	Allen K. Briggs, Chris P. Caputo,
 *			Michael L. Finch, Bradley A. Grantham, and
 *			Lawrence A. Kesteloot
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the Alice Group.
 * 4. The names of the Alice Group or any of its members may not be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE ALICE GROUP ``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 ALICE GROUP 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>
__KERNEL_RCSID(0, "$NetBSD: disksubr.c,v 1.58 2019/04/03 22:10:50 christos Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/disk.h>
#include <sys/disklabel.h>
#include <sys/bootblock.h>
#include <sys/syslog.h>

#include <sys/bswap.h>

#define NUM_PARTS 32

#define ROOT_PART 1
#define UFS_PART 2
#define SWAP_PART 3
#define HFS_PART 4
#define SCRATCH_PART 5

int fat_types[] = {
	MBR_PTYPE_FAT12, MBR_PTYPE_FAT16S,
	MBR_PTYPE_FAT16B, MBR_PTYPE_FAT32,
	MBR_PTYPE_FAT32L, MBR_PTYPE_FAT16L,
	-1
};

static int getFreeLabelEntry(struct disklabel *);
static int whichType(struct part_map_entry *);
static void setpartition(struct part_map_entry *, struct partition *, int);
static int getNamedType(struct part_map_entry *, int, struct disklabel *, int,
	    int, int *);
static char *read_mac_label(char *, struct disklabel *, int *);
static char *read_mbr_label(char *, struct disklabel *, int *);
static const char *read_bsd_label(char *, struct disklabel *, int *);


/*
 * Find an entry in the disk label that is unused and return it
 * or -1 if no entry
 */
static int
getFreeLabelEntry(struct disklabel *lp)
{
	int i;

	for (i = 0; i < MAXPARTITIONS; i++) {
		if ((i != RAW_PART)
		    && (lp->d_partitions[i].p_fstype == FS_UNUSED))
			return i;
	}
	return -1;
}

/*
 * figure out what the type of the given part is and return it
 */
static int
whichType(struct part_map_entry *part)
{
	struct blockzeroblock *bzb;
	char typestr[32], *s;
	int type;

	if (part->pmSig != PART_ENTRY_MAGIC || part->pmPartType[0] == '\0')
		return 0;

	strncpy(typestr, (char *)part->pmPartType, sizeof(typestr));
	typestr[sizeof(typestr) - 1] = '\0';
	for (s = typestr; *s; s++)
		if ((*s >= 'a') && (*s <= 'z'))
			*s = (*s - 'a' + 'A');

	if (strcmp(PART_TYPE_DRIVER, typestr) == 0 ||
	    strcmp(PART_TYPE_DRIVER43, typestr) == 0 ||
	    strcmp(PART_TYPE_DRIVERATA, typestr) == 0 ||
	    strcmp(PART_TYPE_FWB_COMPONENT, typestr) == 0 ||
	    strcmp(PART_TYPE_PARTMAP, typestr) == 0)
		type = 0;
	else if (strcmp(PART_TYPE_UNIX, typestr) == 0) {
		/* unix part, swap, root, usr */
		bzb = (struct blockzeroblock *)(&part->pmBootArgs);
		if (bzb->bzbMagic != BZB_MAGIC)
			type = 0;
		else if (bzb->bzbFlags & BZB_ROOTFS)
			type = ROOT_PART;
		else if (bzb->bzbFlags & BZB_USRFS)
			type = UFS_PART;
		else if (bzb->bzbType == BZB_TYPESWAP)
			type = SWAP_PART;
		else
			type = SCRATCH_PART;
	} else if (strcmp(PART_TYPE_MAC, typestr) == 0)
		type = HFS_PART;
	else
		type = SCRATCH_PART;	/* no known type */

	return type;
}

static void
setpartition(struct part_map_entry *part, struct partition *pp, int fstype)
{
	pp->p_size = part->pmPartBlkCnt;
	pp->p_offset = part->pmPyPartStart;
	pp->p_fstype = fstype;

	part->pmPartType[0] = '\0';
}

static int
getNamedType(struct part_map_entry *part, int num_parts, struct disklabel *lp,
    int type, int alt, int *maxslot)
{
	struct blockzeroblock *bzb;
	int i;

	for (i = 0; i < num_parts; i++) {
		if (whichType(part + i) != type)
			continue;

		if (type == ROOT_PART) {
			bzb = (struct blockzeroblock *)
			    (&(part + i)->pmBootArgs);
			if (alt >= 0 && alt != bzb->bzbCluster)
				continue;
			setpartition(part + i, &lp->d_partitions[0], FS_BSDFFS);
		} else if (type == UFS_PART) {
			bzb = (struct blockzeroblock *)
			    (&(part + i)->pmBootArgs);
			if (alt >= 0 && alt != bzb->bzbCluster)
				continue;
			setpartition(part + i, &lp->d_partitions[6], FS_BSDFFS);
			if (*maxslot < 6)
				*maxslot = 6;
		} else if (type == SWAP_PART) {
			setpartition(part + i, &lp->d_partitions[1], FS_SWAP);
			if (*maxslot < 1)
				*maxslot = 1;
		} else
			printf("disksubr.c: can't do type %d\n", type);

		return 0;
	}

	return -1;
}

/*
 * MF --
 * here's what i'm gonna do:
 * read in the entire diskpartition table, it may be bigger or smaller
 * than NUM_PARTS but read that many entries.  Each entry has a magic
 * number so we'll know if an entry is crap.
 * next fill in the disklabel with info like this
 * next fill in the root, usr, and swap parts.
 * then look for anything else and fit it in.
 *	A: root
 *	B: Swap
 *	C: Whole disk
 *	G: Usr
 *
 *
 * I'm not entirely sure what netbsd386 wants in c & d
 * 386bsd wants other stuff, so i'll leave them alone
 *
 * AKB -- I added to Mike's original algorithm by searching for a bzbCluster
 *	of zero for root, first.  This allows A/UX to live on cluster 1 and
 *	NetBSD to live on cluster 0--regardless of the actual order on the
 *	disk.  This whole algorithm should probably be changed in the future.
 */

/*
 * This uses sector zero.  If this contains what looks like a valid
 * Macintosh boot sector, we attempt to fill in the disklabel structure
 * with the partition data from block #1 on.
 */
static char *
read_mac_label(char *dlbuf, struct disklabel *lp, int *match)
{
	u_int16_t *sbSigp;
	struct part_map_entry *part;
	struct partition *pp;
	char *msg;
	int i, slot, maxslot;

	maxslot = 0;
	*match = 0;
	msg = NULL;

	sbSigp = (u_int16_t *)dlbuf;
	if (*sbSigp != DRIVER_MAP_MAGIC)
		return msg;

	/* Found Macintosh partition magic number; set up disklabel */
	*match = (-1);

	/* the Macintosh partition table starts at sector #1 */
	part = (struct part_map_entry *)(dlbuf + DEV_BSIZE);

	/* Fill in standard partitions */
	lp->d_npartitions = RAW_PART + 1;
	if (getNamedType(part, NUM_PARTS, lp, ROOT_PART, 0, &maxslot))
		getNamedType(part, NUM_PARTS, lp, ROOT_PART, -1, &maxslot);
	if (getNamedType(part, NUM_PARTS, lp, UFS_PART, 0, &maxslot))
		getNamedType(part, NUM_PARTS, lp, UFS_PART, -1, &maxslot);
	getNamedType(part, NUM_PARTS, lp, SWAP_PART, -1, &maxslot);

	/* Now get as many of the rest of the partitions as we can */
	for (i = 0; i < NUM_PARTS; i++) {
		slot = getFreeLabelEntry(lp);
		if (slot < 0)
			break;

		pp = &lp->d_partitions[slot];

		switch (whichType(part + i)) {
		case ROOT_PART:
		/*
		 * another root part will turn into a plain old
		 * UFS_PART partition, live with it.
		 */
		case UFS_PART:
			setpartition(part + i, pp, FS_BSDFFS);
			break;
		case SWAP_PART:
			setpartition(part + i, pp, FS_SWAP);
			break;
		case HFS_PART:
			setpartition(part + i, pp, FS_HFS);
			break;
		case SCRATCH_PART:
			setpartition(part + i, pp, FS_OTHER);
			break;
		default:
			slot = 0;
			break;
		}
		if (slot > maxslot)
			maxslot = slot;
	}
	lp->d_npartitions = ((maxslot >= RAW_PART) ? maxslot : RAW_PART) + 1;
	return msg;
}

/*
 * Scan the disk buffer for a DOS style master boot record.
 * Return if no match; otherwise, set up an in-core disklabel .
 *
 * XXX stuff like this really should be MI
 *
 * Since FFS is endian sensitive, we pay no effort in attempting to
 * dig up *BSD/i386 disk labels that may be present on the disk.
 * Hence anything but DOS partitions is treated as unknown FS type, but
 * this should suffice to mount_msdos Zip and other removable media.
 */
static char *
read_mbr_label(char *dlbuf, struct disklabel *lp, int *match)
{
	struct mbr_partition *dp;
	struct partition *pp;
	char *msg;
	size_t mbr_lbl_off;
	int i, *ip, slot, maxslot;

	maxslot = 0;
	*match = 0;
	msg = NULL;

	if (MBR_MAGIC != bswap16(*(u_int16_t *)(dlbuf + MBR_MAGIC_OFFSET)))
		return msg;

	/* Found MBR magic number; set up disklabel */
	*match = (-1);
	mbr_lbl_off = MBR_BBSECTOR * lp->d_secsize + MBR_PART_OFFSET;
	
	dp = (struct mbr_partition *)(dlbuf + mbr_lbl_off);
	for (i = 0; i < MBR_PART_COUNT; i++, dp++) {
		if (dp->mbrp_type == 0)
			continue;
		
		slot = getFreeLabelEntry(lp);
		maxslot = (slot > maxslot) ? maxslot : slot;
		
		pp = &lp->d_partitions[slot];
		pp->p_fstype = FS_OTHER;
		pp->p_offset = bswap32(dp->mbrp_start);
		pp->p_size = bswap32(dp->mbrp_size);
		
		for (ip = fat_types; *ip != -1; ip++) {
			if (dp->mbrp_type == *ip) {
				pp->p_fstype = FS_MSDOS;
				break;
			}
		}
	}
	lp->d_npartitions = ((maxslot >= RAW_PART) ? maxslot : RAW_PART) + 1;
	return msg;
}

/*
 * Scan the disk buffer in four byte steps for a native BSD disklabel
 * (different ports have variable-sized bootcode before the label)
 */
static const char *
read_bsd_label(char *dlbuf, struct disklabel *lp, int *match)
{
	struct disklabel *dlp;
	const char *msg;
	struct disklabel *blk_start, *blk_end;
	
	*match = 0;
	msg = NULL;

	blk_start = (struct disklabel *)dlbuf;
	blk_end = (struct disklabel *)(dlbuf + (NUM_PARTS << DEV_BSHIFT) -
	    sizeof(struct disklabel));

	for (dlp = blk_start; dlp <= blk_end; 
	     dlp = (struct disklabel *)((char *)dlp + sizeof(long))) {
		if (dlp->d_magic == DISKMAGIC && dlp->d_magic2 == DISKMAGIC) {
			/* Sanity check */
			if (dlp->d_npartitions <= MAXPARTITIONS && 
			    dkcksum(dlp) == 0) {
				*lp = *dlp;
				*match = (-1);
			} else
				msg = "Disk label corrupted";
			break;
		}
	}
	return msg;
}

/*
 * Attempt to read a disk label from a device using the indicated strategy
 * routine.  The label must be partly set up before this: secpercyl and
 * anything required in the strategy routine (e.g., sector size) must be
 * filled in before calling us.  Returns null on success and an error
 * string on failure.
 */
const char *
readdisklabel(dev_t dev, void (*strat)(struct buf *), struct disklabel *lp,
    struct cpu_disklabel *osdep)
{
	struct buf *bp;
	const char *msg;
	int size;

	if (lp->d_secperunit == 0)
		lp->d_secperunit = 0x1fffffff;

	if (lp->d_secpercyl == 0)
		return msg = "Zero secpercyl";

	msg = NULL;

	/* 
	 * Read in the first #(NUM_PARTS + 1) blocks of the disk.
	 * The native Macintosh partition table starts at 
	 * sector #1, but we want #0 too for the BSD label.
	 */

	size = roundup((NUM_PARTS + 1) << DEV_BSHIFT, lp->d_secsize);
	bp = geteblk(size);

	bp->b_dev = dev;
	bp->b_blkno = 0;
	bp->b_resid = 0;
	bp->b_bcount = size;
	bp->b_flags |= B_READ;
	bp->b_cylinder = 1 / lp->d_secpercyl;
	(*strat)(bp);

	if (biowait(bp)) {
		msg = "I/O error reading block zero";
	} else {
		int match;

		/* Add any offsets in the table handlers */
		msg = read_mac_label(bp->b_data, lp, &match);
		if (!match && msg == NULL)
			msg = read_mbr_label(bp->b_data, lp, &match);
		if (!match && msg == NULL)
			msg = read_bsd_label(bp->b_data, lp, &match);
		if (!match && msg == NULL)
			msg = "no disk label";
	}

	brelse(bp, 0);
	return (msg);
}

/*
 * Check new disk label for sensibility before setting it.
 */
int
setdisklabel(struct disklabel *olp, struct disklabel *nlp, u_long openmask,
    struct cpu_disklabel *osdep)
{
	return 0;
}

/*
 * Write disk label back to device after modification.
 *
 *  MF - 8-14-93 This function is never called.  It is here just in case
 *  we want to write dos disklabels some day. Really!
 */
int
writedisklabel(dev_t dev, void (*strat)(struct buf *), struct disklabel *lp,
    struct cpu_disklabel *osdep)
{
#if 0
	struct buf *bp;
	struct disklabel *dlp;
	int labelpart;
	int error = 0;

	labelpart = DISKPART(dev);
	if (lp->d_partitions[labelpart].p_offset != 0) {
		if (lp->d_partitions[0].p_offset != 0)
			return (EXDEV);	/* not quite right */
		labelpart = 0;
	}
	bp = geteblk((int)lp->d_secsize);
	bp->b_dev = MAKEDISKDEV(major(dev), DISKUNIT(dev), labelpart);
	bp->b_blkno = LABELSECTOR;
	bp->b_bcount = lp->d_secsize;
	bp->b_flags |= B_READ;
	(*strat)(bp);
	if (error = biowait(bp))
		goto done;
	for (dlp = (struct disklabel *)bp->b_data;
	    dlp <= (struct disklabel *)
	    ((char *)bp->b_data + lp->d_secsize - sizeof(*dlp));
	    dlp = (struct disklabel *)((char *)dlp + sizeof(long))) {
		if (dlp->d_magic == DISKMAGIC && dlp->d_magic2 == DISKMAGIC &&
		    dkcksum(dlp) == 0) {
			*dlp = *lp;
			bp->b_oflags &= ~(BO_DONE);
			bp->b_flags &= ~(B_READ);
			bp->b_flags |= B_WRITE;
			(*strat)(bp);
			error = biowait(bp);
			goto done;
		}
	}
	error = ESRCH;
done:
	brelse(bp, 0);
	return (error);
#else
	int i;

	/*
	 * Clear and re-analyze the ondisk Apple Disk Partition Map,
	 * then recompute the faked incore disk label. This is necessary
	 * for sysinst, which may have modified the disk layout. We don't
	 * (yet?) support writing real BSD disk labels, so this hack
	 * instead causes the DIOCWDINFO ioctl invoked by sysinst to
	 * update the in-core disk label when it is "written" to disk.
	 * This code was originally developed by Bob Nestor on 9/13/99.
	 */
	lp->d_npartitions = 0;
	for (i = 0; i < MAXPARTITIONS; i++) {
		lp->d_partitions[i].p_fstype = FS_UNUSED;
		lp->d_partitions[i].p_offset = 0;
		if (i != RAW_PART)
			lp->d_partitions[i].p_size = 0;
	}
	return (readdisklabel(dev, strat, lp, osdep) ? EINVAL : 0);
#endif
}
