c309d31
From 6596528e391ad978a6a120142cba97a1d7324cb6 Mon Sep 17 00:00:00 2001
c309d31
From: Seth Forshee <seth.forshee@canonical.com>
c309d31
Date: Mon, 18 Jul 2011 08:06:23 -0700
c309d31
Subject: [PATCH] hfsplus: ensure bio requests are not smaller than the
c309d31
 hardware sectors
c309d31
c309d31
Currently all bio requests are 512 bytes, which may fail for media
c309d31
whose physical sector size is larger than this. Ensure these
c309d31
requests are not smaller than the block device logical block size.
c309d31
c309d31
BugLink: http://bugs.launchpad.net/bugs/734883
c309d31
Signed-off-by: Seth Forshee <seth.forshee@canonical.com>
c309d31
Signed-off-by: Christoph Hellwig <hch@lst.de>
c309d31
---
c309d31
 fs/hfsplus/hfsplus_fs.h |   16 ++++++++-
c309d31
 fs/hfsplus/part_tbl.c   |   32 ++++++++++--------
c309d31
 fs/hfsplus/super.c      |   12 +++---
c309d31
 fs/hfsplus/wrapper.c    |   83 +++++++++++++++++++++++++++++++++++-----------
c309d31
 4 files changed, 101 insertions(+), 42 deletions(-)
c309d31
c309d31
diff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h
c309d31
index 0bebf74..81dfd1e 100644
c309d31
--- a/fs/hfsplus/hfsplus_fs.h
c309d31
+++ b/fs/hfsplus/hfsplus_fs.h
c309d31
@@ -13,6 +13,7 @@
c309d31
 #include <linux/fs.h>
c309d31
 #include <linux/mutex.h>
c309d31
 #include <linux/buffer_head.h>
c309d31
+#include <linux/blkdev.h>
c309d31
 #include "hfsplus_raw.h"
c309d31
 
c309d31
 #define DBG_BNODE_REFS	0x00000001
c309d31
@@ -110,7 +111,9 @@ struct hfsplus_vh;
c309d31
 struct hfs_btree;
c309d31
 
c309d31
 struct hfsplus_sb_info {
c309d31
+	void *s_vhdr_buf;
c309d31
 	struct hfsplus_vh *s_vhdr;
c309d31
+	void *s_backup_vhdr_buf;
c309d31
 	struct hfsplus_vh *s_backup_vhdr;
c309d31
 	struct hfs_btree *ext_tree;
c309d31
 	struct hfs_btree *cat_tree;
c309d31
@@ -258,6 +261,15 @@ struct hfsplus_readdir_data {
c309d31
 	struct hfsplus_cat_key key;
c309d31
 };
c309d31
 
c309d31
+/*
c309d31
+ * Find minimum acceptible I/O size for an hfsplus sb.
c309d31
+ */
c309d31
+static inline unsigned short hfsplus_min_io_size(struct super_block *sb)
c309d31
+{
c309d31
+	return max_t(unsigned short, bdev_logical_block_size(sb->s_bdev),
c309d31
+		     HFSPLUS_SECTOR_SIZE);
c309d31
+}
c309d31
+
c309d31
 #define hfs_btree_open hfsplus_btree_open
c309d31
 #define hfs_btree_close hfsplus_btree_close
c309d31
 #define hfs_btree_write hfsplus_btree_write
c309d31
@@ -436,8 +448,8 @@ int hfsplus_compare_dentry(const struct dentry *parent,
c309d31
 /* wrapper.c */
c309d31
 int hfsplus_read_wrapper(struct super_block *);
c309d31
 int hfs_part_find(struct super_block *, sector_t *, sector_t *);
c309d31
-int hfsplus_submit_bio(struct block_device *bdev, sector_t sector,
c309d31
-		void *data, int rw);
c309d31
+int hfsplus_submit_bio(struct super_block *sb, sector_t sector,
c309d31
+		void *buf, void **data, int rw);
c309d31
 
c309d31
 /* time macros */
c309d31
 #define __hfsp_mt2ut(t)		(be32_to_cpu(t) - 2082844800U)
c309d31
diff --git a/fs/hfsplus/part_tbl.c b/fs/hfsplus/part_tbl.c
c309d31
index 40ad88c..eb355d8 100644
c309d31
--- a/fs/hfsplus/part_tbl.c
c309d31
+++ b/fs/hfsplus/part_tbl.c
c309d31
@@ -88,11 +88,12 @@ static int hfs_parse_old_pmap(struct super_block *sb, struct old_pmap *pm,
c309d31
 	return -ENOENT;
c309d31
 }
c309d31
 
c309d31
-static int hfs_parse_new_pmap(struct super_block *sb, struct new_pmap *pm,
c309d31
-		sector_t *part_start, sector_t *part_size)
c309d31
+static int hfs_parse_new_pmap(struct super_block *sb, void *buf,
c309d31
+		struct new_pmap *pm, sector_t *part_start, sector_t *part_size)
c309d31
 {
c309d31
 	struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
c309d31
 	int size = be32_to_cpu(pm->pmMapBlkCnt);
c309d31
+	int buf_size = hfsplus_min_io_size(sb);
c309d31
 	int res;
c309d31
 	int i = 0;
c309d31
 
c309d31
@@ -107,11 +108,14 @@ static int hfs_parse_new_pmap(struct super_block *sb, struct new_pmap *pm,
c309d31
 		if (++i >= size)
c309d31
 			return -ENOENT;
c309d31
 
c309d31
-		res = hfsplus_submit_bio(sb->s_bdev,
c309d31
-					 *part_start + HFS_PMAP_BLK + i,
c309d31
-					 pm, READ);
c309d31
-		if (res)
c309d31
-			return res;
c309d31
+		pm = (struct new_pmap *)((u8 *)pm + HFSPLUS_SECTOR_SIZE);
c309d31
+		if ((u8 *)pm - (u8 *)buf >= buf_size) {
c309d31
+			res = hfsplus_submit_bio(sb,
c309d31
+						 *part_start + HFS_PMAP_BLK + i,
c309d31
+						 buf, (void **)&pm, READ);
c309d31
+			if (res)
c309d31
+				return res;
c309d31
+		}
c309d31
 	} while (pm->pmSig == cpu_to_be16(HFS_NEW_PMAP_MAGIC));
c309d31
 
c309d31
 	return -ENOENT;
c309d31
@@ -124,15 +128,15 @@ static int hfs_parse_new_pmap(struct super_block *sb, struct new_pmap *pm,
c309d31
 int hfs_part_find(struct super_block *sb,
c309d31
 		sector_t *part_start, sector_t *part_size)
c309d31
 {
c309d31
-	void *data;
c309d31
+	void *buf, *data;
c309d31
 	int res;
c309d31
 
c309d31
-	data = kmalloc(HFSPLUS_SECTOR_SIZE, GFP_KERNEL);
c309d31
-	if (!data)
c309d31
+	buf = kmalloc(hfsplus_min_io_size(sb), GFP_KERNEL);
c309d31
+	if (!buf)
c309d31
 		return -ENOMEM;
c309d31
 
c309d31
-	res = hfsplus_submit_bio(sb->s_bdev, *part_start + HFS_PMAP_BLK,
c309d31
-				 data, READ);
c309d31
+	res = hfsplus_submit_bio(sb, *part_start + HFS_PMAP_BLK,
c309d31
+				 buf, &data, READ);
c309d31
 	if (res)
c309d31
 		goto out;
c309d31
 
c309d31
@@ -141,13 +145,13 @@ int hfs_part_find(struct super_block *sb,
c309d31
 		res = hfs_parse_old_pmap(sb, data, part_start, part_size);
c309d31
 		break;
c309d31
 	case HFS_NEW_PMAP_MAGIC:
c309d31
-		res = hfs_parse_new_pmap(sb, data, part_start, part_size);
c309d31
+		res = hfs_parse_new_pmap(sb, buf, data, part_start, part_size);
c309d31
 		break;
c309d31
 	default:
c309d31
 		res = -ENOENT;
c309d31
 		break;
c309d31
 	}
c309d31
 out:
c309d31
-	kfree(data);
c309d31
+	kfree(buf);
c309d31
 	return res;
c309d31
 }
c309d31
diff --git a/fs/hfsplus/super.c b/fs/hfsplus/super.c
c309d31
index 84f56e1..c106ca2 100644
c309d31
--- a/fs/hfsplus/super.c
c309d31
+++ b/fs/hfsplus/super.c
c309d31
@@ -203,17 +203,17 @@ int hfsplus_sync_fs(struct super_block *sb, int wait)
c309d31
 		write_backup = 1;
c309d31
 	}
c309d31
 
c309d31
-	error2 = hfsplus_submit_bio(sb->s_bdev,
c309d31
+	error2 = hfsplus_submit_bio(sb,
c309d31
 				   sbi->part_start + HFSPLUS_VOLHEAD_SECTOR,
c309d31
-				   sbi->s_vhdr, WRITE_SYNC);
c309d31
+				   sbi->s_vhdr_buf, NULL, WRITE_SYNC);
c309d31
 	if (!error)
c309d31
 		error = error2;
c309d31
 	if (!write_backup)
c309d31
 		goto out;
c309d31
 
c309d31
-	error2 = hfsplus_submit_bio(sb->s_bdev,
c309d31
+	error2 = hfsplus_submit_bio(sb,
c309d31
 				  sbi->part_start + sbi->sect_count - 2,
c309d31
-				  sbi->s_backup_vhdr, WRITE_SYNC);
c309d31
+				  sbi->s_backup_vhdr_buf, NULL, WRITE_SYNC);
c309d31
 	if (!error)
c309d31
 		error2 = error;
c309d31
 out:
c309d31
@@ -257,8 +257,8 @@ static void hfsplus_put_super(struct super_block *sb)
c309d31
 	hfs_btree_close(sbi->ext_tree);
c309d31
 	iput(sbi->alloc_file);
c309d31
 	iput(sbi->hidden_dir);
c309d31
-	kfree(sbi->s_vhdr);
c309d31
-	kfree(sbi->s_backup_vhdr);
c309d31
+	kfree(sbi->s_vhdr_buf);
c309d31
+	kfree(sbi->s_backup_vhdr_buf);
c309d31
 	unload_nls(sbi->nls);
c309d31
 	kfree(sb->s_fs_info);
c309d31
 	sb->s_fs_info = NULL;
c309d31
diff --git a/fs/hfsplus/wrapper.c b/fs/hfsplus/wrapper.c
c309d31
index 2f933e8..10e515a 100644
c309d31
--- a/fs/hfsplus/wrapper.c
c309d31
+++ b/fs/hfsplus/wrapper.c
c309d31
@@ -31,25 +31,67 @@ static void hfsplus_end_io_sync(struct bio *bio, int err)
c309d31
 	complete(bio->bi_private);
c309d31
 }
c309d31
 
c309d31
-int hfsplus_submit_bio(struct block_device *bdev, sector_t sector,
c309d31
-		void *data, int rw)
c309d31
+/*
c309d31
+ * hfsplus_submit_bio - Perfrom block I/O
c309d31
+ * @sb: super block of volume for I/O
c309d31
+ * @sector: block to read or write, for blocks of HFSPLUS_SECTOR_SIZE bytes
c309d31
+ * @buf: buffer for I/O
c309d31
+ * @data: output pointer for location of requested data
c309d31
+ * @rw: direction of I/O
c309d31
+ *
c309d31
+ * The unit of I/O is hfsplus_min_io_size(sb), which may be bigger than
c309d31
+ * HFSPLUS_SECTOR_SIZE, and @buf must be sized accordingly. On reads
c309d31
+ * @data will return a pointer to the start of the requested sector,
c309d31
+ * which may not be the same location as @buf.
c309d31
+ *
c309d31
+ * If @sector is not aligned to the bdev logical block size it will
c309d31
+ * be rounded down. For writes this means that @buf should contain data
c309d31
+ * that starts at the rounded-down address. As long as the data was
c309d31
+ * read using hfsplus_submit_bio() and the same buffer is used things
c309d31
+ * will work correctly.
c309d31
+ */
c309d31
+int hfsplus_submit_bio(struct super_block *sb, sector_t sector,
c309d31
+		void *buf, void **data, int rw)
c309d31
 {
c309d31
 	DECLARE_COMPLETION_ONSTACK(wait);
c309d31
 	struct bio *bio;
c309d31
 	int ret = 0;
c309d31
+	unsigned int io_size;
c309d31
+	loff_t start;
c309d31
+	int offset;
c309d31
+
c309d31
+	/*
c309d31
+	 * Align sector to hardware sector size and find offset. We
c309d31
+	 * assume that io_size is a power of two, which _should_
c309d31
+	 * be true.
c309d31
+	 */
c309d31
+	io_size = hfsplus_min_io_size(sb);
c309d31
+	start = (loff_t)sector << HFSPLUS_SECTOR_SHIFT;
c309d31
+	offset = start & (io_size - 1);
c309d31
+	sector &= ~((io_size >> HFSPLUS_SECTOR_SHIFT) - 1);
c309d31
 
c309d31
 	bio = bio_alloc(GFP_NOIO, 1);
c309d31
 	bio->bi_sector = sector;
c309d31
-	bio->bi_bdev = bdev;
c309d31
+	bio->bi_bdev = sb->s_bdev;
c309d31
 	bio->bi_end_io = hfsplus_end_io_sync;
c309d31
 	bio->bi_private = &wait;
c309d31
 
c309d31
-	/*
c309d31
-	 * We always submit one sector at a time, so bio_add_page must not fail.
c309d31
-	 */
c309d31
-	if (bio_add_page(bio, virt_to_page(data), HFSPLUS_SECTOR_SIZE,
c309d31
-			 offset_in_page(data)) != HFSPLUS_SECTOR_SIZE)
c309d31
-		BUG();
c309d31
+	if (!(rw & WRITE) && data)
c309d31
+		*data = (u8 *)buf + offset;
c309d31
+
c309d31
+	while (io_size > 0) {
c309d31
+		unsigned int page_offset = offset_in_page(buf);
c309d31
+		unsigned int len = min_t(unsigned int, PAGE_SIZE - page_offset,
c309d31
+					 io_size);
c309d31
+
c309d31
+		ret = bio_add_page(bio, virt_to_page(buf), len, page_offset);
c309d31
+		if (ret != len) {
c309d31
+			ret = -EIO;
c309d31
+			goto out;
c309d31
+		}
c309d31
+		io_size -= len;
c309d31
+		buf = (u8 *)buf + len;
c309d31
+	}
c309d31
 
c309d31
 	submit_bio(rw, bio);
c309d31
 	wait_for_completion(&wait);
c309d31
@@ -57,8 +99,9 @@ int hfsplus_submit_bio(struct block_device *bdev, sector_t sector,
c309d31
 	if (!bio_flagged(bio, BIO_UPTODATE))
c309d31
 		ret = -EIO;
c309d31
 
c309d31
+out:
c309d31
 	bio_put(bio);
c309d31
-	return ret;
c309d31
+	return ret < 0 ? ret : 0;
c309d31
 }
c309d31
 
c309d31
 static int hfsplus_read_mdb(void *bufptr, struct hfsplus_wd *wd)
c309d31
@@ -143,17 +186,17 @@ int hfsplus_read_wrapper(struct super_block *sb)
c309d31
 		goto out;
c309d31
 
c309d31
 	error = -ENOMEM;
c309d31
-	sbi->s_vhdr = kmalloc(HFSPLUS_SECTOR_SIZE, GFP_KERNEL);
c309d31
-	if (!sbi->s_vhdr)
c309d31
+	sbi->s_vhdr_buf = kmalloc(hfsplus_min_io_size(sb), GFP_KERNEL);
c309d31
+	if (!sbi->s_vhdr_buf)
c309d31
 		goto out;
c309d31
-	sbi->s_backup_vhdr = kmalloc(HFSPLUS_SECTOR_SIZE, GFP_KERNEL);
c309d31
-	if (!sbi->s_backup_vhdr)
c309d31
+	sbi->s_backup_vhdr_buf = kmalloc(hfsplus_min_io_size(sb), GFP_KERNEL);
c309d31
+	if (!sbi->s_backup_vhdr_buf)
c309d31
 		goto out_free_vhdr;
c309d31
 
c309d31
 reread:
c309d31
-	error = hfsplus_submit_bio(sb->s_bdev,
c309d31
-				   part_start + HFSPLUS_VOLHEAD_SECTOR,
c309d31
-				   sbi->s_vhdr, READ);
c309d31
+	error = hfsplus_submit_bio(sb, part_start + HFSPLUS_VOLHEAD_SECTOR,
c309d31
+				   sbi->s_vhdr_buf, (void **)&sbi->s_vhdr,
c309d31
+				   READ);
c309d31
 	if (error)
c309d31
 		goto out_free_backup_vhdr;
c309d31
 
c309d31
@@ -183,9 +226,9 @@ reread:
c309d31
 		goto reread;
c309d31
 	}
c309d31
 
c309d31
-	error = hfsplus_submit_bio(sb->s_bdev,
c309d31
-				   part_start + part_size - 2,
c309d31
-				   sbi->s_backup_vhdr, READ);
c309d31
+	error = hfsplus_submit_bio(sb, part_start + part_size - 2,
c309d31
+				   sbi->s_backup_vhdr_buf,
c309d31
+				   (void **)&sbi->s_backup_vhdr, READ);
c309d31
 	if (error)
c309d31
 		goto out_free_backup_vhdr;
c309d31
 
c309d31
-- 
c309d31
1.7.6
c309d31