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