History

block: add bio_truncate to fix guard_bio_eod

Some filesystem, such as vfat, may send bio which crosses device boundary, and the worse thing is that the IO request starting within device boundaries can contain more than one segment past EOD. Commit dce30ca9e3b6 ("fs: fix guard_bio_eod to check for real EOD errors") tries to fix this issue by returning -EIO for this situation. However, this way lets fs user code lose chance to handle -EIO, then sync_inodes_sb() may hang for ever. Also the current truncating on last segment is dangerous by updating the last bvec, given bvec table becomes not immutable any more, and fs bio users may not retrieve the truncated pages via bio_for_each_segment_all() in its .end_io callback. Fixes this issue by supporting multi-segment truncating. And the approach is simpler: - just update bio size since block layer can make correct bvec with the updated bio size. Then bvec table becomes really immutable. - zero all truncated segments for read bio

Ming Lei committed 6 years ago

Commit 85a8ce61 parent b2c0fcd3 files · +41 −24Browse files

Unified · Split

3 files
  1. block/bio.c +39 −0
  2. fs/buffer.c +1 −24
  3. include/linux/bio.h +1 −0
block/bio.c+39−0
@@ -538,6 +538,45 @@ void zero_fill_bio_iter(struct bio *bio, struct bvec_iter start) · 537 unmodified lines
538}538}
539EXPORT_SYMBOL(zero_fill_bio_iter);539EXPORT_SYMBOL(zero_fill_bio_iter);
540 540
541void bio_truncate(struct bio *bio, unsigned new_size)
542{
543 struct bio_vec bv;
544 struct bvec_iter iter;
545 unsigned int done = 0;
546 bool truncated = false;
547
548 if (new_size >= bio->bi_iter.bi_size)
549 return;
550
551 if (bio_data_dir(bio) != READ)
552 goto exit;
553
554 bio_for_each_segment(bv, bio, iter) {
555 if (done + bv.bv_len > new_size) {
556 unsigned offset;
557
558 if (!truncated)
559 offset = new_size - done;
560 else
561 offset = 0;
562 zero_user(bv.bv_page, offset, bv.bv_len - offset);
563 truncated = true;
564 }
565 done += bv.bv_len;
566 }
567
568 exit:
569 /*
570 * Don't touch bvec table here and make it really immutable, since
571 * fs bio user has to retrieve all pages via bio_for_each_segment_all
572 * in its .end_bio() callback.
573 *
574 * It is enough to truncate bio by updating .bi_size since we can make
575 * correct bvec with the updated .bi_size for drivers.
576 */
577 bio->bi_iter.bi_size = new_size;
578}
579
541/**580/**
542 * bio_put - release a reference to a bio581 * bio_put - release a reference to a bio
543 * @bio: bio to release reference to582 * @bio: bio to release reference to
fs/buffer.c+1−24
@@ -3034,8 +3034,6 @@ static void end_bio_bh_io_sync(struct bio *bio) · 3033 unmodified lines
3034void guard_bio_eod(int op, struct bio *bio)3034void guard_bio_eod(int op, struct bio *bio)
3035{3035{
3036 sector_t maxsector;3036 sector_t maxsector;
3037 struct bio_vec *bvec = bio_last_bvec_all(bio);
3038 unsigned truncated_bytes;
3039 struct hd_struct *part;3037 struct hd_struct *part;
3040 3038
3041 rcu_read_lock();3039 rcu_read_lock();
@@ -3061,28 +3059,7 @@ void guard_bio_eod(int op, struct bio *bio) · 19 unmodified lines
3061 if (likely((bio->bi_iter.bi_size >> 9) <= maxsector))3059 if (likely((bio->bi_iter.bi_size >> 9) <= maxsector))
3062 return;3060 return;
3063 3061
3064 /* Uhhuh. We've got a bio that straddles the device size! */3062 bio_truncate(bio, maxsector << 9);
3065 truncated_bytes = bio->bi_iter.bi_size - (maxsector << 9);
3066
3067 /*
3068 * The bio contains more than one segment which spans EOD, just return
3069 * and let IO layer turn it into an EIO
3070 */
3071 if (truncated_bytes > bvec->bv_len)
3072 return;
3073
3074 /* Truncate the bio.. */
3075 bio->bi_iter.bi_size -= truncated_bytes;
3076 bvec->bv_len -= truncated_bytes;
3077
3078 /* ..and clear the end of the buffer for reads */
3079 if (op == REQ_OP_READ) {
3080 struct bio_vec bv;
3081
3082 mp_bvec_last_segment(bvec, &bv);
3083 zero_user(bv.bv_page, bv.bv_offset + bv.bv_len,
3084 truncated_bytes);
3085 }
3086}3063}
3087 3064
3088static int submit_bh_wbc(int op, int op_flags, struct buffer_head *bh,3065static int submit_bh_wbc(int op, int op_flags, struct buffer_head *bh,
include/linux/bio.h+1−0
@@ -470,6 +470,7 @@ extern struct bio *bio_copy_user_iov(struct request_queue *, · 469 unmodified lines
470 gfp_t);470 gfp_t);
471extern int bio_uncopy_user(struct bio *);471extern int bio_uncopy_user(struct bio *);
472void zero_fill_bio_iter(struct bio *bio, struct bvec_iter iter);472void zero_fill_bio_iter(struct bio *bio, struct bvec_iter iter);
473void bio_truncate(struct bio *bio, unsigned new_size);
473 474
474static inline void zero_fill_bio(struct bio *bio)475static inline void zero_fill_bio(struct bio *bio)
475{476{