/*
 *  fs/partitions/msdos.c
 *
 *  Code extracted from drivers/block/genhd.c
 *  Copyright (C) 1991-1998  Linus Torvalds
 *
 *  Thanks to Branko Lankester, lankeste@fwi.uva.nl, who found a bug
 *  in the early extended-partition checks and added DM partitions
 *
 *  Support for DiskManager v6.0x added by Mark Lord,
 *  with information provided by OnTrack.  This now works for linux fdisk
 *  and LILO, as well as loadlin and bootln.  Note that disks other than
 *  /dev/hda *must* have a "DOS" type 0x51 partition in the first slot (hda1).
 *
 *  More flexible handling of extended partitions - aeb, 950831
 *
 *  Check partition table on IDE disks for common CHS translations
 *
 *  Re-organised Feb 1998 Russell King
 */

#include <linux/config.h>
#include <linux/fs.h>
#include <linux/genhd.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/string.h>
#include <linux/blk.h>

#ifdef CONFIG_BLK_DEV_IDE
#include <linux/ide.h>	/* IDE xlate */
#endif /* CONFIG_BLK_DEV_IDE */

#include <asm/system.h>

#include "check.h"
#include "msdos.h"

#if CONFIG_BLK_DEV_MD
extern void md_autodetect_dev(kdev_t dev);
#endif

static int current_minor;

/*
 * Many architectures don't like unaligned accesses, which is
 * frequently the case with the nr_sects and start_sect partition
 * table entries.
 */
#include <asm/unaligned.h>

#define SYS_IND(p)	(get_unaligned(&p->sys_ind))
#define NR_SECTS(p)	({ __typeof__(p->nr_sects) __a =	\
				get_unaligned(&p->nr_sects);	\
				le32_to_cpu(__a); \
			})

#define START_SECT(p)	({ __typeof__(p->start_sect) __a =	\
				get_unaligned(&p->start_sect);	\
				le32_to_cpu(__a); \
			})

static inline int is_extended_partition(struct partition *p)
{
	return (SYS_IND(p) == DOS_EXTENDED_PARTITION ||
		SYS_IND(p) == WIN98_EXTENDED_PARTITION ||
		SYS_IND(p) == LINUX_EXTENDED_PARTITION);
}

/*
 * partition_name() formats the short partition name into the supplied
 * buffer, and returns a pointer to that buffer.
 * Used by several partition types which makes conditional inclusion messy,
 * use __attribute__ ((unused)) instead.
 */
static char __attribute__ ((unused))
	*partition_name (struct gendisk *hd, int minor, char *buf)
{
#ifdef CONFIG_DEVFS_FS
	sprintf(buf, "p%d", (minor & ((1 << hd->minor_shift) - 1)));
	return buf;
#else
	return disk_name(hd, minor, buf);
#endif
}

#define MSDOS_LABEL_MAGIC1	0x55
#define MSDOS_LABEL_MAGIC2	0xAA

static inline int
msdos_magic_present(unsigned char *p) {
	return (p[0] == MSDOS_LABEL_MAGIC1 && p[1] == MSDOS_LABEL_MAGIC2);
}

/*
 * Create devices for each logical partition in an extended partition.
 * The logical partitions form a linked list, with each entry being
 * a partition table with two entries.  The first entry
 * is the real data partition (with a start relative to the partition
 * table start).  The second is a pointer to the next logical partition
 * (with a start relative to the entire extended partition).
 * We do not create a Linux partition for the partition tables, but
 * only for the actual data partitions.
 */

static void extended_partition(struct gendisk *hd, kdev_t dev)
{
	struct buffer_head *bh;
	struct partition *p;
	unsigned long first_sector, first_size, this_sector, this_size;
	int mask = (1 << hd->minor_shift) - 1;
	int sector_size = get_hardsect_size(dev) / 512;
	int loopct = 0;		/* number of links followed
				   without finding a data partition */
	int i;

	first_sector = hd->part[MINOR(dev)].start_sect;
	first_size = hd->part[MINOR(dev)].nr_sects;
	this_sector = first_sector;

	while (1) {
		if (++loopct > 100)
			return;
		if ((current_minor & mask) == 0)
			return;
		if (!(bh = bread(dev,0,get_ptable_blocksize(dev))))
			return;

		if (!msdos_magic_present(bh->b_data + 510))
			goto done;

		p = (struct partition *) (bh->b_data + 0x1be);

		this_size = hd->part[MINOR(dev)].nr_sects;

		/*
		 * Usually, the first entry is the real data partition,
		 * the 2nd entry is the next extended partition, or empty,
		 * and the 3rd and 4th entries are unused.
		 * However, DRDOS sometimes has the extended partition as
		 * the first entry (when the data partition is empty),
		 * and OS/2 seems to use all four entries.
		 */

		/* 
		 * First process the data partition(s)
		 */
		for (i=0; i<4; i++, p++) {
			if (!NR_SECTS(p) || is_extended_partition(p))
				continue;

			/* Check the 3rd and 4th entries -
			   these sometimes contain random garbage */
			if (i >= 2
				&& START_SECT(p) + NR_SECTS(p) > this_size
				&& (this_sector + START_SECT(p) < first_sector ||
				    this_sector + START_SECT(p) + NR_SECTS(p) >
				     first_sector + first_size))
				continue;

			add_gd_partition(hd, current_minor,
					 this_sector+START_SECT(p)*sector_size,
					 NR_SECTS(p)*sector_size);
#if CONFIG_BLK_DEV_MD
			if (SYS_IND(p) == LINUX_RAID_PARTITION) {
			    md_autodetect_dev(MKDEV(hd->major,current_minor));
			}
#endif

			current_minor++;
			loopct = 0;
			if ((current_minor & mask) == 0)
				goto done;
		}
		/*
		 * Next, process the (first) extended partition, if present.
		 * (So far, there seems to be no reason to make
		 *  extended_partition()  recursive and allow a tree
		 *  of extended partitions.)
		 * It should be a link to the next logical partition.
		 * Create a minor for this just long enough to get the next
		 * partition table.  The minor will be reused for the next
		 * data partition.
		 */
		p -= 4;
		for (i=0; i<4; i++, p++)
			if(NR_SECTS(p) && is_extended_partition(p))
				break;
		if (i == 4)
			goto done;	 /* nothing left to do */

		hd->part[current_minor].nr_sects = NR_SECTS(p) * sector_size; /* JSt */
		hd->part[current_minor].start_sect = first_sector + START_SECT(p) * sector_size;
		this_sector = first_sector + START_SECT(p) * sector_size;
		dev = MKDEV(hd->major, current_minor);

		/* Use bforget(), as we have changed the disk geometry */
		bforget(bh);
	}
done:
	bforget(bh);
}

static inline struct buffer_head *
get_partition_table_block(struct gendisk *hd, int minor, int blocknr) {
	kdev_t dev = MKDEV(hd->major, minor);
	return bread(dev, blocknr, get_ptable_blocksize(dev));
}

#ifdef CONFIG_SOLARIS_X86_PARTITION

/* james@bpgc.com: Solaris has a nasty indicator: 0x82 which also
   indicates linux swap.  Be careful before believing this is Solaris. */

static void
solaris_x86_partition(struct gendisk *hd, int minor) {
	long offset = hd->part[minor].start_sect;

	struct buffer_head *bh;
	struct solaris_x86_vtoc *v;
	struct solaris_x86_slice *s;
	int mask = (1 << hd->minor_shift) - 1;
	int i;
	char buf[40];

	if(!(bh = get_partition_table_block(hd, minor, 0)))
		return;
	v = (struct solaris_x86_vtoc *)(bh->b_data + 512);
	if(le32_to_cpu(v->v_sanity) != SOLARIS_X86_VTOC_SANE) {
		brelse(bh);
		return;
	}
	printk(" %s: <solaris:", partition_name(hd, minor, buf));
	if(le32_to_cpu(v->v_version) != 1) {
		printk("  cannot handle version %d vtoc>\n",
			le32_to_cpu(v->v_version));
		brelse(bh);
		return;
	}
	for(i=0; i<SOLARIS_X86_NUMSLICE; i++) {
		if ((current_minor & mask) == 0)
			break;
		s = &v->v_slice[i];

		if (s->s_size == 0)
			continue;
		printk(" [s%d]", i);
		/* solaris partitions are relative to current MS-DOS
		 * one but add_gd_partition starts relative to sector
		 * zero of the disk.  Therefore, must add the offset
		 * of the current partition */
		add_gd_partition(hd, current_minor, le32_to_cpu(s->s_start)+offset,
				 le32_to_cpu(s->s_size));
		current_minor++;
	}
	brelse(bh);
	printk(" >\n");
}
#endif

#ifdef CONFIG_BSD_DISKLABEL
static void
check_and_add_bsd_partition(struct gendisk *hd,
			    struct bsd_partition *bsd_p, int minor) {
	struct hd_struct *lin_p;
		/* check relative position of partitions.  */
	for (lin_p = hd->part + 1 + minor;
	     lin_p - hd->part - minor < current_minor; lin_p++) {
			/* no relationship -> try again */
		if (lin_p->start_sect + lin_p->nr_sects <= le32_to_cpu(bsd_p->p_offset) ||
		    lin_p->start_sect >= le32_to_cpu(bsd_p->p_offset) + le32_to_cpu(bsd_p->p_size))
			continue;	
			/* equal -> no need to add */
		if (lin_p->start_sect == le32_to_cpu(bsd_p->p_offset) && 
			lin_p->nr_sects == le32_to_cpu(bsd_p->p_size)) 
			return;
			/* bsd living within dos partition */
		if (lin_p->start_sect <= le32_to_cpu(bsd_p->p_offset) && lin_p->start_sect 
			+ lin_p->nr_sects >= le32_to_cpu(bsd_p->p_offset) + le32_to_cpu(bsd_p->p_size)) {
#ifdef DEBUG_BSD_DISKLABEL
			printk("w: %d %ld+%ld,%d+%d", 
				lin_p - hd->part, 
				lin_p->start_sect, lin_p->nr_sects, 
				le32_to_cpu(bsd_p->p_offset),
				le32_to_cpu(bsd_p->p_size));
#endif
			break;
		}
	 /* ouch: bsd and linux overlap. Don't even try for that partition */
#ifdef DEBUG_BSD_DISKLABEL
		printk("???: %d %ld+%ld,%d+%d",
			lin_p - hd->part, lin_p->start_sect, lin_p->nr_sects,
			le32_to_cpu(bsd_p->p_offset), le32_to_cpu(bsd_p->p_size));
#endif
		printk("???");
		return;
	} /* if the bsd partition is not currently known to linux, we end
	   * up here 
	   */
	add_gd_partition(hd, current_minor, le32_to_cpu(bsd_p->p_offset),
			 le32_to_cpu(bsd_p->p_size));
	current_minor++;
}

/* 
 * Create devices for BSD partitions listed in a disklabel, under a
 * dos-like partition. See extended_partition() for more information.
 */
static void bsd_disklabel_partition(struct gendisk *hd, int minor, int type) {
	struct buffer_head *bh;
	struct bsd_disklabel *l;
	struct bsd_partition *p;
	int max_partitions;
	int mask = (1 << hd->minor_shift) - 1;
	char buf[40];

	if (!(bh = get_partition_table_block(hd, minor, 0)))
		return;
	l = (struct bsd_disklabel *) (bh->b_data+512);
	if (le32_to_cpu(l->d_magic) != BSD_DISKMAGIC) {
		brelse(bh);
		return;
	}
	printk(" %s:", partition_name(hd, minor, buf));
	printk((type == OPENBSD_PARTITION) ? " <openbsd:" :
	       (type == NETBSD_PARTITION) ? " <netbsd:" : " <bsd:");

	max_partitions = ((type == OPENBSD_PARTITION) ? OPENBSD_MAXPARTITIONS
			                              : BSD_MAXPARTITIONS);
	if (le16_to_cpu(l->d_npartitions) < max_partitions)
		max_partitions = le16_to_cpu(l->d_npartitions);
	for (p = l->d_partitions; p - l->d_partitions <  max_partitions; p++) {
		if ((current_minor & mask) == 0)
			break;

		if (p->p_fstype != BSD_FS_UNUSED) 
			check_and_add_bsd_partition(hd, p, minor);
	}

	/* Use bforget(), as we have changed the disk setup */
	bforget(bh);

	printk(" >\n");
}
#endif

#ifdef CONFIG_UNIXWARE_DISKLABEL
/*
 * Create devices for Unixware partitions listed in a disklabel, under a
 * dos-like partition. See extended_partition() for more information.
 */
static void unixware_partition(struct gendisk *hd, int minor) {
	struct buffer_head *bh;
	struct unixware_disklabel *l;
	struct unixware_slice *p;
	int mask = (1 << hd->minor_shift) - 1;
	char buf[40];

	if (!(bh = get_partition_table_block(hd, minor, 14)))
		return;
	l = (struct unixware_disklabel *) (bh->b_data+512);
	if (le32_to_cpu(l->d_magic) != UNIXWARE_DISKMAGIC ||
	    le32_to_cpu(l->vtoc.v_magic) != UNIXWARE_DISKMAGIC2) {
		brelse(bh);
		return;
	}
	printk(" %s: <unixware:", partition_name(hd, minor, buf));
	p = &l->vtoc.v_slice[1];
	/* I omit the 0th slice as it is the same as whole disk. */
	while (p - &l->vtoc.v_slice[0] < UNIXWARE_NUMSLICE) {
		if ((current_minor & mask) == 0)
			break;

		if (p->s_label != UNIXWARE_FS_UNUSED) {
			add_gd_partition(hd, current_minor, START_SECT(p),
					 NR_SECTS(p));
			current_minor++;
		}
		p++;
	}
	/* Use bforget, as we have changed the disk setup */
	bforget(bh);
	printk(" >\n");
}
#endif

#ifdef CONFIG_MINIX_SUBPARTITION
/*
 * Minix 2.0.0/2.0.2 subpartition support.
 * Anand Krishnamurthy <anandk@wiproge.med.ge.com>
 * Rajeev V. Pillai    <rajeevvp@yahoo.com>
 */
static void minix_partition(struct gendisk *hd, int minor)
{
	struct buffer_head *bh;
	struct partition *p;
	int mask = (1 << hd->minor_shift) - 1;
	int i;
	char buf[40];

	if (!(bh = get_partition_table_block(hd, minor, 0)))
		return;
	bh->b_state = 0;

	p = (struct partition *)(bh->b_data + 0x1be);

	/* The first sector of a Minix partition can have either
	 * a secondary MBR describing its subpartitions, or
	 * the normal boot sector. */
	if (msdos_magic_present (bh->b_data + 510) &&
	    SYS_IND(p) == MINIX_PARTITION) { /* subpartition table present */

		printk(" %s: <minix:", partition_name(hd, minor, buf));
		for (i = 0; i < MINIX_NR_SUBPARTITIONS; i++, p++) {
			if ((current_minor & mask) == 0)
				break;
			/* add each partition in use */
			if (SYS_IND(p) == MINIX_PARTITION) {
				add_gd_partition(hd, current_minor,
					      START_SECT(p), NR_SECTS(p));
				current_minor++;
			}
		}
		printk(" >\n");
	}
	brelse(bh);
}
#endif /* CONFIG_MINIX_SUBPARTITION */
 
int msdos_partition(struct gendisk *hd, kdev_t dev,
		    unsigned long first_sector, int first_part_minor) {
	int i, minor = current_minor = first_part_minor;
	struct buffer_head *bh;
	struct partition *p;
	unsigned char *data;
	int mask = (1 << hd->minor_shift) - 1;
	int sector_size = get_hardsect_size(dev) / 512;
#ifdef CONFIG_BLK_DEV_IDE
	int tested_for_xlate = 0;

read_mbr:
#endif /* CONFIG_BLK_DEV_IDE */
	if (!(bh = bread(dev,0,get_ptable_blocksize(dev)))) {
		if (warn_no_part) printk(" unable to read partition table\n");
		return -1;
	}
	data = bh->b_data;
#ifdef CONFIG_BLK_DEV_IDE
check_table:
#endif /* CONFIG_BLK_DEV_IDE */
	/* Use bforget(), because we may have changed the disk geometry */
	if (!msdos_magic_present(data + 510)) {
		bforget(bh);
		return 0;
	}
	p = (struct partition *) (data + 0x1be);

#ifdef CONFIG_BLK_DEV_IDE
	if (!tested_for_xlate++) {	/* Do this only once per disk */
		/*
		 * Look for various forms of IDE disk geometry translation
		 */
		unsigned int sig = le16_to_cpu(*(unsigned short *)(data + 2));
		int heads = 0;
		/*
		 * The i386 partition handling programs very often
		 * make partitions end on cylinder boundaries.
		 * There is no need to do so, and Linux fdisk doesnt always
		 * do this, and Windows NT on Alpha doesnt do this either,
		 * but still, this helps to guess #heads.
		 */
		for (i = 0; i < 4; i++) {
			struct partition *q = &p[i];
			if (NR_SECTS(q)) {
				if ((q->sector & 63) == 1 &&
				    (q->end_sector & 63) == 63)
					heads = q->end_head + 1;
				break;
			}
		}
		if (SYS_IND(p) == EZD_PARTITION) {
			/*
			 * Accesses to sector 0 must go to sector 1 instead.
			 */
			if (ide_xlate_1024(dev, -1, heads, " [EZD]")) {
				data += 512;
				goto check_table;
			}
		} else if (SYS_IND(p) == DM6_PARTITION) {

			/*
			 * Everything on the disk is offset by 63 sectors,
			 * including a "new" MBR with its own partition table.
			 */
			if (ide_xlate_1024(dev, 1, heads, " [DM6:DDO]")) {
				bforget(bh);
				goto read_mbr;	/* start over with new MBR */
			}
		} else if (sig <= 0x1ae &&
			   data[sig] == 0xAA && data[sig+1] == 0x55 &&
			   (data[sig+2] & 1)) {
			/* DM6 signature in MBR, courtesy of OnTrack */
			(void) ide_xlate_1024 (dev, 0, heads, " [DM6:MBR]");
		} else if (SYS_IND(p) == DM6_AUX1PARTITION ||
			   SYS_IND(p) == DM6_AUX3PARTITION) {
			/*
			 * DM6 on other than the first (boot) drive
			 */
			(void) ide_xlate_1024(dev, 0, heads, " [DM6:AUX]");
		} else {
			(void) ide_xlate_1024(dev, 2, heads, " [PTBL]");
		}
	}
#endif /* CONFIG_BLK_DEV_IDE */

	/* Look for partitions in two passes:
	   First find the primary partitions, and the DOS-type extended partitions.
	   On the second pass look inside *BSD and Unixware and Solaris partitions. */

	current_minor += 4;  /* first "extra" minor (for extended partitions) */
	for (i=1 ; i<=4 ; minor++,i++,p++) {
		if (!NR_SECTS(p))
			continue;
		add_gd_partition(hd, minor, first_sector+START_SECT(p)*sector_size,
				 NR_SECTS(p)*sector_size);
#if CONFIG_BLK_DEV_MD
		if (SYS_IND(p) == LINUX_RAID_PARTITION) {
			md_autodetect_dev(MKDEV(hd->major,minor));
		}
#endif
		if (is_extended_partition(p)) {
			printk(" <");
			/*
			 * If we are rereading the partition table, we need
			 * to set the size of the partition so that we will
			 * be able to bread the block containing the extended
			 * partition info.
			 */
			hd->sizes[minor] = hd->part[minor].nr_sects 
			  	>> (BLOCK_SIZE_BITS - 9);
			extended_partition(hd, MKDEV(hd->major, minor));
			printk(" >");
			/* prevent someone doing mkfs or mkswap on an
			   extended partition, but leave room for LILO */
			if (hd->part[minor].nr_sects > 2)
				hd->part[minor].nr_sects = 2;
		}
	}

	/*
	 *  Check for old-style Disk Manager partition table
	 */
	if (msdos_magic_present(data + 0xfc)) {
		p = (struct partition *) (0x1be + data);
		for (i = 4 ; i < 16 ; i++, current_minor++) {
			p--;
			if ((current_minor & mask) == 0)
				break;
			if (!(START_SECT(p) && NR_SECTS(p)))
				continue;
			add_gd_partition(hd, current_minor, START_SECT(p), NR_SECTS(p));
		}
	}
	printk("\n");

	/* second pass - output for each on a separate line */
	minor -= 4;
	p = (struct partition *) (0x1be + data);
	for (i=1 ; i<=4 ; minor++,i++,p++) {
		if (!NR_SECTS(p))
			continue;
#ifdef CONFIG_BSD_DISKLABEL
		if (SYS_IND(p) == BSD_PARTITION ||
		    SYS_IND(p) == NETBSD_PARTITION ||
		    SYS_IND(p) == OPENBSD_PARTITION)
			bsd_disklabel_partition(hd, minor, SYS_IND(p));
#endif
#ifdef CONFIG_MINIX_SUBPARTITION
		if (SYS_IND(p) == MINIX_PARTITION) {
			minix_partition(hd, minor);
		}
#endif
#ifdef CONFIG_UNIXWARE_DISKLABEL
		if (SYS_IND(p) == UNIXWARE_PARTITION)
			unixware_partition(hd, minor);
#endif
#ifdef CONFIG_SOLARIS_X86_PARTITION
		if(SYS_IND(p) == SOLARIS_X86_PARTITION)
			solaris_x86_partition(hd, minor);
#endif
	}

	bforget(bh);
	return 1;
}
