Skip to content

Commit 85a8ce6

Browse files
Ming Leiaxboe
authored andcommitted
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 dce30ca ("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 Cc: Carlos Maiolino <[email protected]> Cc: [email protected] Fixed-by: dce30ca ("fs: fix guard_bio_eod to check for real EOD errors") Reported-by: [email protected] Signed-off-by: Ming Lei <[email protected]> Signed-off-by: Jens Axboe <[email protected]>
1 parent b2c0fcd commit 85a8ce6

File tree

3 files changed

+41
-24
lines changed

3 files changed

+41
-24
lines changed

block/bio.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,45 @@ void zero_fill_bio_iter(struct bio *bio, struct bvec_iter start)
538538
}
539539
EXPORT_SYMBOL(zero_fill_bio_iter);
540540

541+
void 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+
541580
/**
542581
* bio_put - release a reference to a bio
543582
* @bio: bio to release reference to

fs/buffer.c

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3034,8 +3034,6 @@ static void end_bio_bh_io_sync(struct bio *bio)
30343034
void guard_bio_eod(int op, struct bio *bio)
30353035
{
30363036
sector_t maxsector;
3037-
struct bio_vec *bvec = bio_last_bvec_all(bio);
3038-
unsigned truncated_bytes;
30393037
struct hd_struct *part;
30403038

30413039
rcu_read_lock();
@@ -3061,28 +3059,7 @@ void guard_bio_eod(int op, struct bio *bio)
30613059
if (likely((bio->bi_iter.bi_size >> 9) <= maxsector))
30623060
return;
30633061

3064-
/* Uhhuh. We've got a bio that straddles the device size! */
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-
}
3062+
bio_truncate(bio, maxsector << 9);
30863063
}
30873064

30883065
static int submit_bh_wbc(int op, int op_flags, struct buffer_head *bh,

include/linux/bio.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,7 @@ extern struct bio *bio_copy_user_iov(struct request_queue *,
470470
gfp_t);
471471
extern int bio_uncopy_user(struct bio *);
472472
void zero_fill_bio_iter(struct bio *bio, struct bvec_iter iter);
473+
void bio_truncate(struct bio *bio, unsigned new_size);
473474

474475
static inline void zero_fill_bio(struct bio *bio)
475476
{

0 commit comments

Comments
 (0)