X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=fs%2Fbio.c;h=0ebd86b4ad9f6f6008867b3a424e2e60a4bb60b3;hb=c7b5ebbddf7bcd3651947760f423e3783bbe6573;hp=e246f542aa0628823d28d93f9f0437fa539ea370;hpb=a2c21200f1c81b08cb55e417b68150bba439b646;p=linux-2.6.git diff --git a/fs/bio.c b/fs/bio.c index e246f542a..0ebd86b4a 100644 --- a/fs/bio.c +++ b/fs/bio.c @@ -137,33 +137,29 @@ inline void bio_init(struct bio *bio) **/ struct bio *bio_alloc(int gfp_mask, int nr_iovecs) { - struct bio_vec *bvl = NULL; - unsigned long idx; - struct bio *bio; - - bio = mempool_alloc(bio_pool, gfp_mask); - if (unlikely(!bio)) - goto out; + struct bio *bio = mempool_alloc(bio_pool, gfp_mask); - bio_init(bio); + if (likely(bio)) { + struct bio_vec *bvl = NULL; - if (unlikely(!nr_iovecs)) - goto noiovec; + bio_init(bio); + if (likely(nr_iovecs)) { + unsigned long idx; - bvl = bvec_alloc(gfp_mask, nr_iovecs, &idx); - if (bvl) { - bio->bi_flags |= idx << BIO_POOL_OFFSET; - bio->bi_max_vecs = bvec_array[idx].nr_vecs; -noiovec: + bvl = bvec_alloc(gfp_mask, nr_iovecs, &idx); + if (unlikely(!bvl)) { + mempool_free(bio, bio_pool); + bio = NULL; + goto out; + } + bio->bi_flags |= idx << BIO_POOL_OFFSET; + bio->bi_max_vecs = bvec_array[idx].nr_vecs; + } bio->bi_io_vec = bvl; bio->bi_destructor = bio_destructor; -out: - return bio; } - - mempool_free(bio, bio_pool); - bio = NULL; - goto out; +out: + return bio; } /** @@ -376,6 +372,38 @@ int bio_add_page(struct bio *bio, struct page *page, unsigned int len, len, offset); } +struct bio_map_data { + struct bio_vec *iovecs; + void __user *userptr; +}; + +static void bio_set_map_data(struct bio_map_data *bmd, struct bio *bio) +{ + memcpy(bmd->iovecs, bio->bi_io_vec, sizeof(struct bio_vec) * bio->bi_vcnt); + bio->bi_private = bmd; +} + +static void bio_free_map_data(struct bio_map_data *bmd) +{ + kfree(bmd->iovecs); + kfree(bmd); +} + +static struct bio_map_data *bio_alloc_map_data(int nr_segs) +{ + struct bio_map_data *bmd = kmalloc(sizeof(*bmd), GFP_KERNEL); + + if (!bmd) + return NULL; + + bmd->iovecs = kmalloc(sizeof(struct bio_vec) * nr_segs, GFP_KERNEL); + if (bmd->iovecs) + return bmd; + + kfree(bmd); + return NULL; +} + /** * bio_uncopy_user - finish previously mapped bio * @bio: bio being terminated @@ -385,23 +413,22 @@ int bio_add_page(struct bio *bio, struct page *page, unsigned int len, */ int bio_uncopy_user(struct bio *bio) { + struct bio_map_data *bmd = bio->bi_private; + const int read = bio_data_dir(bio) == READ; struct bio_vec *bvec; int i, ret = 0; - if (bio_data_dir(bio) == READ) { - char *uaddr = bio->bi_private; - - __bio_for_each_segment(bvec, bio, i, 0) { - char *addr = page_address(bvec->bv_page); + __bio_for_each_segment(bvec, bio, i, 0) { + char *addr = page_address(bvec->bv_page); + unsigned int len = bmd->iovecs[i].bv_len; - if (!ret && copy_to_user(uaddr, addr, bvec->bv_len)) - ret = -EFAULT; + if (read && !ret && copy_to_user(bmd->userptr, addr, len)) + ret = -EFAULT; - __free_page(bvec->bv_page); - uaddr += bvec->bv_len; - } + __free_page(bvec->bv_page); + bmd->userptr += len; } - + bio_free_map_data(bmd); bio_put(bio); return ret; } @@ -422,14 +449,25 @@ struct bio *bio_copy_user(request_queue_t *q, unsigned long uaddr, { unsigned long end = (uaddr + len + PAGE_SIZE - 1) >> PAGE_SHIFT; unsigned long start = uaddr >> PAGE_SHIFT; + struct bio_map_data *bmd; struct bio_vec *bvec; struct page *page; struct bio *bio; int i, ret; + bmd = bio_alloc_map_data(end - start); + if (!bmd) + return ERR_PTR(-ENOMEM); + + bmd->userptr = (void __user *) uaddr; + bio = bio_alloc(GFP_KERNEL, end - start); - if (!bio) + if (!bio) { + bio_free_map_data(bmd); return ERR_PTR(-ENOMEM); + } + + bio->bi_rw |= (!write_to_vm << BIO_RW); ret = 0; while (len) { @@ -452,31 +490,30 @@ struct bio *bio_copy_user(request_queue_t *q, unsigned long uaddr, len -= bytes; } + if (ret) + goto cleanup; + /* * success */ - if (!ret) { - if (!write_to_vm) { - bio->bi_rw |= (1 << BIO_RW); - /* - * for a write, copy in data to kernel pages - */ - ret = -EFAULT; - bio_for_each_segment(bvec, bio, i) { - char *addr = page_address(bvec->bv_page); + if (!write_to_vm) { + char __user *p = (char __user *) uaddr; - if (copy_from_user(addr, (char *) uaddr, bvec->bv_len)) - goto cleanup; - } - } + /* + * for a write, copy in data to kernel pages + */ + ret = -EFAULT; + bio_for_each_segment(bvec, bio, i) { + char *addr = page_address(bvec->bv_page); - bio->bi_private = (void *) uaddr; - return bio; + if (copy_from_user(addr, p, bvec->bv_len)) + goto cleanup; + p += bvec->bv_len; + } } - /* - * cleanup - */ + bio_set_map_data(bmd, bio); + return bio; cleanup: bio_for_each_segment(bvec, bio, i) __free_page(bvec->bv_page); @@ -608,18 +645,6 @@ static void __bio_unmap_user(struct bio *bio) struct bio_vec *bvec; int i; - /* - * find original bio if it was bounced - */ - if (bio->bi_private) { - /* - * someone stole our bio, must not happen - */ - BUG_ON(!bio_flagged(bio, BIO_BOUNCED)); - - bio = bio->bi_private; - } - /* * make sure we dirty pages we wrote to */