#include <linux/blkdev.h>
#include <linux/ctype.h>
#include <linux/fs_uuid.h>
#include <linux/slab.h>

static int debug_enabled;

#define PRINTK(fmt, args...) do {					\
	if (debug_enabled)						\
		printk(KERN_DEBUG fmt, ## args);			\
	} while(0)

#define PRINT_HEX_DUMP(v1, v2, v3, v4, v5, v6, v7, v8)			\
	do {								\
		if (debug_enabled)					\
			print_hex_dump(v1, v2, v3, v4, v5, v6, v7, v8);	\
	} while(0)

/*
 * Simple UUID translation
 */

struct uuid_info {
	const char *key;
	const char *name;
	long bkoff;
	unsigned sboff;
	unsigned sig_len;
	const char *magic;
	int uuid_offset;
	int last_mount_offset;
	int last_mount_size;
};

/*
 * Based on libuuid's blkid_magic array. Note that I don't
 * have uuid offsets for all of these yet - mssing ones are 0x0.
 * Further information welcome.
 *
 * Rearranged by page of fs signature for optimisation.
 */
static struct uuid_info uuid_list[] = {
 { NULL, "oracleasm", 0, 32, 8, "ORCLDISK", 0x0, 0, 0 },
 { "ntfs", "ntfs", 0, 3, 8, "NTFS    ", 0x0, 0, 0 },
 { "vfat", "vfat", 0, 0x52, 5, "MSWIN", 0x0, 0, 0 },
 { "vfat", "vfat", 0, 0x52, 8, "FAT32   ", 0x0, 0, 0 },
 { "vfat", "vfat", 0, 0x36, 5, "MSDOS", 0x0, 0, 0 },
 { "vfat", "vfat", 0, 0x36, 8, "FAT16   ", 0x0, 0, 0 },
 { "vfat", "vfat", 0, 0x36, 8, "FAT12   ", 0x0, 0, 0 },
 { "vfat", "vfat", 0, 0, 1, "\353", 0x0, 0, 0 },
 { "vfat", "vfat", 0, 0, 1, "\351", 0x0, 0, 0 },
 { "vfat", "vfat", 0, 0x1fe, 2, "\125\252", 0x0, 0, 0 },
 { "xfs", "xfs", 0, 0, 4, "XFSB", 0x20, 0, 0 },
 { "romfs", "romfs", 0, 0, 8, "-rom1fs-", 0x0, 0, 0 },
 { "bfs", "bfs", 0, 0, 4, "\316\372\173\033", 0, 0, 0 },
 { "cramfs", "cramfs", 0, 0, 4, "E=\315\050", 0x0, 0, 0 },
 { "qnx4", "qnx4", 0, 4, 6, "QNX4FS", 0, 0, 0 },
 { NULL, "crypt_LUKS", 0, 0, 6, "LUKS\xba\xbe", 0x0, 0, 0 },
 { "squashfs", "squashfs", 0, 0, 4, "sqsh", 0, 0, 0 },
 { "squashfs", "squashfs", 0, 0, 4, "hsqs", 0, 0, 0 },
 { "ocfs", "ocfs", 0, 8, 9, "OracleCFS", 0x0, 0, 0 },
 { "lvm2pv", "lvm2pv", 0, 0x018, 8, "LVM2 001", 0x0, 0, 0 },
 { "sysv", "sysv", 0, 0x3f8, 4, "\020~\030\375", 0, 0, 0 },
 { "ext", "ext", 1, 0x38, 2, "\123\357", 0x468, 0x42c, 4 },
 { "minix", "minix", 1, 0x10, 2, "\177\023", 0, 0, 0 },
 { "minix", "minix", 1, 0x10, 2, "\217\023", 0, 0, 0 },
 { "minix", "minix", 1, 0x10, 2, "\150\044", 0, 0, 0 },
 { "minix", "minix", 1, 0x10, 2, "\170\044", 0, 0, 0 },
 { "lvm2pv", "lvm2pv", 1, 0x018, 8, "LVM2 001", 0x0, 0, 0 },
 { "vxfs", "vxfs", 1, 0, 4, "\365\374\001\245", 0, 0, 0 },
 { "hfsplus", "hfsplus", 1, 0, 2, "BD", 0x0, 0, 0 },
 { "hfsplus", "hfsplus", 1, 0, 2, "H+", 0x0, 0, 0 },
 { "hfsplus", "hfsplus", 1, 0, 2, "HX", 0x0, 0, 0 },
 { "hfs", "hfs", 1, 0, 2, "BD", 0x0, 0, 0 },
 { "ocfs2", "ocfs2", 1, 0, 6, "OCFSV2", 0x0, 0, 0 },
 { "lvm2pv", "lvm2pv", 0, 0x218, 8, "LVM2 001", 0x0, 0, 0 },
 { "lvm2pv", "lvm2pv", 1, 0x218, 8, "LVM2 001", 0x0, 0, 0 },
 { "ocfs2", "ocfs2", 2, 0, 6, "OCFSV2", 0x0, 0, 0 },
 { "swap", "swap", 0, 0xff6, 10, "SWAP-SPACE", 0x40c, 0, 0 },
 { "swap", "swap", 0, 0xff6, 10, "SWAPSPACE2", 0x40c, 0, 0 },
 { "swap", "swsuspend", 0, 0xff6, 9, "S1SUSPEND", 0x40c, 0, 0 },
 { "swap", "swsuspend", 0, 0xff6, 9, "S2SUSPEND", 0x40c, 0, 0 },
 { "swap", "swsuspend", 0, 0xff6, 9, "ULSUSPEND", 0x40c, 0, 0 },
 { "ocfs2", "ocfs2", 4, 0, 6, "OCFSV2", 0x0, 0, 0 },
 { "ocfs2", "ocfs2", 8, 0, 6, "OCFSV2", 0x0, 0, 0 },
 { "hpfs", "hpfs", 8, 0, 4, "I\350\225\371", 0, 0, 0 },
 { "reiserfs", "reiserfs", 8, 0x34, 8, "ReIsErFs", 0x10054, 0, 0 },
 { "reiserfs", "reiserfs", 8, 20, 8, "ReIsErFs", 0x10054, 0, 0 },
 { "zfs", "zfs", 8, 0, 8, "\0\0\x02\xf5\xb0\x07\xb1\x0c", 0x0, 0, 0 },
 { "zfs", "zfs", 8, 0, 8, "\x0c\xb1\x07\xb0\xf5\x02\0\0", 0x0, 0, 0 },
 { "ufs", "ufs", 8, 0x55c, 4, "T\031\001\000", 0, 0, 0 },
 { "swap", "swap", 0, 0x1ff6, 10, "SWAP-SPACE", 0x40c, 0, 0 },
 { "swap", "swap", 0, 0x1ff6, 10, "SWAPSPACE2", 0x40c, 0, 0 },
 { "swap", "swsuspend", 0, 0x1ff6, 9, "S1SUSPEND", 0x40c, 0, 0 },
 { "swap", "swsuspend", 0, 0x1ff6, 9, "S2SUSPEND", 0x40c, 0, 0 },
 { "swap", "swsuspend", 0, 0x1ff6, 9, "ULSUSPEND", 0x40c, 0, 0 },
 { "reiserfs", "reiserfs", 64, 0x34, 9, "ReIsEr2Fs", 0x10054, 0, 0 },
 { "reiserfs", "reiserfs", 64, 0x34, 9, "ReIsEr3Fs", 0x10054, 0, 0 },
 { "reiserfs", "reiserfs", 64, 0x34, 8, "ReIsErFs", 0x10054, 0, 0 },
 { "reiser4", "reiser4", 64, 0, 7, "ReIsEr4", 0x100544, 0, 0 },
 { "gfs2", "gfs2", 64, 0, 4, "\x01\x16\x19\x70", 0x0, 0, 0 },
 { "gfs", "gfs", 64, 0, 4, "\x01\x16\x19\x70", 0x0, 0, 0 },
 { "btrfs", "btrfs", 64, 0x40, 8, "_BHRfS_M", 0x0, 0, 0 },
 { "swap", "swap", 0, 0x3ff6, 10, "SWAP-SPACE", 0x40c, 0, 0 },
 { "swap", "swap", 0, 0x3ff6, 10, "SWAPSPACE2", 0x40c, 0, 0 },
 { "swap", "swsuspend", 0, 0x3ff6, 9, "S1SUSPEND", 0x40c, 0, 0 },
 { "swap", "swsuspend", 0, 0x3ff6, 9, "S2SUSPEND", 0x40c, 0, 0 },
 { "swap", "swsuspend", 0, 0x3ff6, 9, "ULSUSPEND", 0x40c, 0, 0 },
 { "udf", "udf", 32, 1, 5, "BEA01", 0x0, 0, 0 },
 { "udf", "udf", 32, 1, 5, "BOOT2", 0x0, 0, 0 },
 { "udf", "udf", 32, 1, 5, "CD001", 0x0, 0, 0 },
 { "udf", "udf", 32, 1, 5, "CDW02", 0x0, 0, 0 },
 { "udf", "udf", 32, 1, 5, "NSR02", 0x0, 0, 0 },
 { "udf", "udf", 32, 1, 5, "NSR03", 0x0, 0, 0 },
 { "udf", "udf", 32, 1, 5, "TEA01", 0x0, 0, 0 },
 { "iso9660", "iso9660", 32, 1, 5, "CD001", 0x0, 0, 0 },
 { "iso9660", "iso9660", 32, 9, 5, "CDROM", 0x0, 0, 0 },
 { "jfs", "jfs", 32, 0, 4, "JFS1", 0x88, 0, 0 },
 { "swap", "swap", 0, 0x7ff6, 10, "SWAP-SPACE", 0x40c, 0, 0 },
 { "swap", "swap", 0, 0x7ff6, 10, "SWAPSPACE2", 0x40c, 0, 0 },
 { "swap", "swsuspend", 0, 0x7ff6, 9, "S1SUSPEND", 0x40c, 0, 0 },
 { "swap", "swsuspend", 0, 0x7ff6, 9, "S2SUSPEND", 0x40c, 0, 0 },
 { "swap", "swsuspend", 0, 0x7ff6, 9, "ULSUSPEND", 0x40c, 0, 0 },
 { "swap", "swap", 0, 0xfff6, 10, "SWAP-SPACE", 0x40c, 0, 0 },
 { "swap", "swap", 0, 0xfff6, 10, "SWAPSPACE2", 0x40c, 0, 0 },
 { "swap", "swsuspend", 0, 0xfff6, 9, "S1SUSPEND", 0x40c, 0, 0 },
 { "swap", "swsuspend", 0, 0xfff6, 9, "S2SUSPEND", 0x40c, 0, 0 },
 { "swap", "swsuspend", 0, 0xfff6, 9, "ULSUSPEND", 0x40c, 0, 0 },
 { "zfs", "zfs", 264, 0, 8, "\0\0\x02\xf5\xb0\x07\xb1\x0c", 0x0, 0, 0 },
 { "zfs", "zfs", 264, 0, 8, "\x0c\xb1\x07\xb0\xf5\x02\0\0", 0x0, 0, 0 },
 { NULL, NULL, 0, 0, 0, NULL, 0x0, 0, 0 }
};

static int null_uuid(const char *uuid)
{
	int i;

	for (i = 0; i < 16 && !uuid[i]; i++);

	return (i == 16);
}


static void uuid_end_bio(struct bio *bio, int err)
{
	struct page *page = bio->bi_io_vec[0].bv_page;

	if(!test_bit(BIO_UPTODATE, &bio->bi_flags))
		SetPageError(page);

	unlock_page(page);
	bio_put(bio);
}


/**
 * submit - submit BIO request
 * @dev: The block device we're using.
 * @page_num: The page we're reading.
 *
 * Based on Patrick Mochell's pmdisk code from long ago: "Straight from the
 * textbook - allocate and initialize the bio. If we're writing, make sure
 * the page is marked as dirty. Then submit it and carry on."
 **/
static struct page *read_bdev_page(struct block_device *dev, int page_num)
{
	struct bio *bio = NULL;
	struct page *page = alloc_page(GFP_NOFS);

	if (!page) {
		printk(KERN_ERR "Failed to allocate a page for reading data "
				"in UUID checks.");
		return NULL;
	}

	bio = bio_alloc(GFP_NOFS, 1);
	bio->bi_bdev = dev;
	bio->bi_sector = page_num << 3;
	bio->bi_end_io = uuid_end_bio;

	PRINTK("Submitting bio on device %lx, page %d.\n",
			(unsigned long) dev->bd_dev, page_num);

	if (bio_add_page(bio, page, PAGE_SIZE, 0) < PAGE_SIZE) {
		printk(KERN_DEBUG "ERROR: adding page to bio at %d\n",
				page_num);
		bio_put(bio);
		__free_page(page);
		printk(KERN_DEBUG "read_bdev_page freed page %p (in error "
				"path).\n", page);
		return NULL;
	}

	lock_page(page);
	submit_bio(READ | REQ_SYNC | REQ_UNPLUG, bio);

	wait_on_page_locked(page);
	if (PageError(page)) {
		__free_page(page);
		page = NULL;
	}
	return page;
}

int bdev_matches_key(struct block_device *bdev, const char *key)
{
	unsigned char *data = NULL;
	struct page *data_page = NULL;

	int dev_offset, pg_num, pg_off, i;
	int last_pg_num = -1;
	int result = 0;
	char buf[50];

	if (null_uuid(key)) {
		PRINTK("Refusing to find a NULL key.\n");
		return 0;
	}

	if (!bdev->bd_disk) {
		bdevname(bdev, buf);
		PRINTK("bdev %s has no bd_disk.\n", buf);
		return 0;
	}

	if (!bdev->bd_disk->queue) {
		bdevname(bdev, buf);
		PRINTK("bdev %s has no queue.\n", buf);
		return 0;
	}

	for (i = 0; uuid_list[i].name; i++) {
		struct uuid_info *dat = &uuid_list[i];

		if (!dat->key || strcmp(dat->key, key))
			continue;

		dev_offset = (dat->bkoff << 10) + dat->sboff;
		pg_num = dev_offset >> 12;
		pg_off = dev_offset & 0xfff;

		if ((((pg_num + 1) << 3) - 1) > bdev->bd_part->nr_sects >> 1)
			continue;

		if (pg_num != last_pg_num) {
			if (data_page)
				__free_page(data_page);
			data_page = read_bdev_page(bdev, pg_num);
			if (!data_page)
				continue;
			data = page_address(data_page);
		}

		last_pg_num = pg_num;

		if (strncmp(&data[pg_off], dat->magic, dat->sig_len))
			continue;

		result = 1;
		break;
	}

	if (data_page)
		__free_page(data_page);

	return result;
}

/* 
 * part_matches_fs_info - Does the given partition match the details given?
 *
 * Returns a score saying how good the match is.
 * 0 = no UUID match.
 * 1 = UUID but last mount time differs.
 * 2 = UUID, last mount time but not dev_t
 * 3 = perfect match
 *
 * This lets us cope elegantly with probing resulting in dev_ts changing
 * from boot to boot, and with the case where a user copies a partition
 * (UUID is non unique), and we need to check the last mount time of the
 * correct partition.
 */
int part_matches_fs_info(struct hd_struct *part, struct fs_info *seek)
{
	struct block_device *bdev;
	struct fs_info *got;
	int result = 0;
	char buf[50];

	if (null_uuid((char *) &seek->uuid)) {
		PRINTK("Refusing to find a NULL uuid.\n");
		return 0;
	}

	bdev = bdget(part_devt(part));

	PRINTK("part_matches fs info considering %x.\n", part_devt(part));

	if (blkdev_get(bdev, FMODE_READ)) {
		PRINTK("blkdev_get failed.\n");
		return 0;
	}

	if (!bdev->bd_disk) {
		bdevname(bdev, buf);
		PRINTK("bdev %s has no bd_disk.\n", buf);
		goto out;
	}

	if (!bdev->bd_disk->queue) {
		bdevname(bdev, buf);
		PRINTK("bdev %s has no queue.\n", buf);
		goto out;
	}

	got = fs_info_from_block_dev(bdev);

	if (got && !memcmp(got->uuid, seek->uuid, 16)) {
		PRINTK(" Have matching UUID.\n");
		PRINTK(" Got: LMS %d, LM %p.\n", got->last_mount_size, got->last_mount);
		PRINTK(" Seek: LMS %d, LM %p.\n", seek->last_mount_size, seek->last_mount);
		result = 1;

		if (got->last_mount_size == seek->last_mount_size &&
		    got->last_mount && seek->last_mount &&
		    !memcmp(got->last_mount, seek->last_mount,
			    got->last_mount_size)) {
			result = 2;

			PRINTK(" Matching last mount time.\n");

			if (part_devt(part) == seek->dev_t) {
				result = 3;
				PRINTK(" Matching dev_t.\n");
			} else
				PRINTK("Dev_ts differ (%x vs %x).\n", part_devt(part), seek->dev_t);
		}
	}

	PRINTK(" Score for %x is %d.\n", part_devt(part), result);
	free_fs_info(got);
out:
	blkdev_put(bdev, FMODE_READ);
	return result;
}

void free_fs_info(struct fs_info *fs_info)
{
	if (!fs_info || IS_ERR(fs_info))
		return;

	if (fs_info->last_mount)
		kfree(fs_info->last_mount);

	kfree(fs_info);
}
EXPORT_SYMBOL_GPL(free_fs_info);

struct fs_info *fs_info_from_block_dev(struct block_device *bdev)
{
	unsigned char *data = NULL;
	struct page *data_page = NULL;

	int dev_offset, pg_num, pg_off;
	int uuid_pg_num, uuid_pg_off, i;
	unsigned char *uuid_data = NULL;
	struct page *uuid_data_page = NULL;

	int last_pg_num = -1, last_uuid_pg_num = 0;
	char buf[50];
	struct fs_info *fs_info = NULL;

	bdevname(bdev, buf);

	PRINTK("uuid_from_block_dev looking for partition type of %s.\n", buf);

	for (i = 0; uuid_list[i].name; i++) {
		struct uuid_info *dat = &uuid_list[i];
		dev_offset = (dat->bkoff << 10) + dat->sboff;
		pg_num = dev_offset >> 12;
		pg_off = dev_offset & 0xfff;
		uuid_pg_num = dat->uuid_offset >> 12;
		uuid_pg_off = dat->uuid_offset & 0xfff;

		if ((((pg_num + 1) << 3) - 1) > bdev->bd_part->nr_sects >> 1)
			continue;

		/* Ignore partition types with no UUID offset */
		if (!dat->uuid_offset)
			continue;

		if (pg_num != last_pg_num) {
			if (data_page)
				__free_page(data_page);
			data_page = read_bdev_page(bdev, pg_num);
			if (!data_page)
				continue;
			data = page_address(data_page);
		}

		last_pg_num = pg_num;

		if (strncmp(&data[pg_off], dat->magic, dat->sig_len))
			continue;

		PRINTK("This partition looks like %s.\n", dat->name);

		fs_info = kzalloc(sizeof(struct fs_info), GFP_KERNEL);

		if (!fs_info) {
			PRINTK("Failed to allocate fs_info struct.");
			fs_info = ERR_PTR(-ENOMEM);
			break;
		}

		/* UUID can't be off the end of the disk */
		if ((uuid_pg_num > bdev->bd_part->nr_sects >> 3) ||
				!dat->uuid_offset)
			goto no_uuid;

		if (!uuid_data || uuid_pg_num != last_uuid_pg_num) {
			/* No need to reread the page from above */
			if (uuid_pg_num == pg_num && uuid_data)
				memcpy(uuid_data, data, PAGE_SIZE);
			else {
				if (uuid_data_page)
					__free_page(uuid_data_page);
				uuid_data_page = read_bdev_page(bdev, uuid_pg_num);
				if (!uuid_data_page)
					continue;
				uuid_data = page_address(uuid_data_page);
			}
		}

		last_uuid_pg_num = uuid_pg_num;
		memcpy(&fs_info->uuid, &uuid_data[uuid_pg_off], 16);
		fs_info->dev_t = bdev->bd_dev;

no_uuid:
		PRINT_HEX_DUMP(KERN_EMERG, "fs_info_from_block_dev "
				"returning uuid ", DUMP_PREFIX_NONE, 16, 1,
				fs_info->uuid, 16, 0);

		if (dat->last_mount_size) {
			int pg = dat->last_mount_offset >> 12, sz;
			int off = dat->last_mount_offset & 0xfff;
			struct page *last_mount = read_bdev_page(bdev, pg);
			unsigned char *last_mount_data;
			char *ptr;

			if (!last_mount) {
				fs_info = ERR_PTR(-ENOMEM);
				break;
			}
			last_mount_data = page_address(last_mount);
			sz = dat->last_mount_size;
			ptr = kmalloc(sz, GFP_KERNEL);

			if (!ptr) {
				printk(KERN_EMERG "fs_info_from_block_dev "
					"failed to get memory for last mount "
					"timestamp.");
				free_fs_info(fs_info);
				fs_info = ERR_PTR(-ENOMEM);
			} else {
				fs_info->last_mount = ptr;
				fs_info->last_mount_size = sz;
				memcpy(ptr, &last_mount_data[off], sz);
			}

			__free_page(last_mount);
		}
		break;
	}

	if (data_page)
		__free_page(data_page);

	if (uuid_data_page)
		__free_page(uuid_data_page);

	return fs_info;
}
EXPORT_SYMBOL_GPL(fs_info_from_block_dev);

static int __init uuid_debug_setup(char *str)
{
	int value;

	if (sscanf(str, "=%d", &value))
		debug_enabled = value;

	return 1;
}

__setup("uuid_debug", uuid_debug_setup);
