summaryrefslogtreecommitdiffstats
path: root/drivers/block/loop/loop_main.c
diff options
context:
space:
mode:
authorManuel Bentele2019-07-07 23:14:46 +0200
committerManuel Bentele2019-07-07 23:14:46 +0200
commit01f03f8bf618b8f73c01ac749dd61ca0c24ca800 (patch)
tree77304d6b3d68ee01fa66889ad938b5afd7faa1ca /drivers/block/loop/loop_main.c
parentblock: loop: rename and move files for file format support integration (diff)
downloadkernel-qcow2-linux-01f03f8bf618b8f73c01ac749dd61ca0c24ca800.tar.gz
kernel-qcow2-linux-01f03f8bf618b8f73c01ac749dd61ca0c24ca800.tar.xz
kernel-qcow2-linux-01f03f8bf618b8f73c01ac749dd61ca0c24ca800.zip
block: loop: add file format subsystem for loop devices
The loop device is extended by a file format subsystem to allow the implementation of various disk file formats. The file format drivers are implemented as own kernel modules and registered by the subsystem. The subsystem takes control over the specified file format at loop creation and calls the corresponding file format driver functions. At the moment, the file format subsystem can handle ... - read - write - discard - flush ... operations of the loop block device. To implement any disk file format in the future, the interface of the subsystem must be extended by a size and capacity functionality. This is especially necessary for sparse disk file formats. Signed-off-by: Manuel Bentele <development@manuel-bentele.de>
Diffstat (limited to 'drivers/block/loop/loop_main.c')
-rw-r--r--drivers/block/loop/loop_main.c394
1 files changed, 40 insertions, 354 deletions
diff --git a/drivers/block/loop/loop_main.c b/drivers/block/loop/loop_main.c
index 53a2f7b92492..a346a68d0fd7 100644
--- a/drivers/block/loop/loop_main.c
+++ b/drivers/block/loop/loop_main.c
@@ -79,6 +79,7 @@
#include <linux/ioprio.h>
#include <linux/blk-cgroup.h>
+#include "loop_file_fmt.h"
#include "loop_main.h"
#include <linux/uaccess.h>
@@ -246,219 +247,6 @@ figure_loop_size(struct loop_device *lo, loff_t offset, loff_t sizelimit)
return 0;
}
-static inline int
-lo_do_transfer(struct loop_device *lo, int cmd,
- struct page *rpage, unsigned roffs,
- struct page *lpage, unsigned loffs,
- int size, sector_t rblock)
-{
- int ret;
-
- ret = lo->transfer(lo, cmd, rpage, roffs, lpage, loffs, size, rblock);
- if (likely(!ret))
- return 0;
-
- printk_ratelimited(KERN_ERR
- "loop: Transfer error at byte offset %llu, length %i.\n",
- (unsigned long long)rblock << 9, size);
- return ret;
-}
-
-static inline void loop_iov_iter_bvec(struct iov_iter *i,
- unsigned int direction, const struct bio_vec *bvec,
- unsigned long nr_segs, size_t count)
-{
- iov_iter_bvec(i, direction, bvec, nr_segs, count);
- i->type |= ITER_BVEC_FLAG_NO_REF;
-}
-
-static int lo_write_bvec(struct file *file, struct bio_vec *bvec, loff_t *ppos)
-{
- struct iov_iter i;
- ssize_t bw;
-
- loop_iov_iter_bvec(&i, WRITE, bvec, 1, bvec->bv_len);
-
- file_start_write(file);
- bw = vfs_iter_write(file, &i, ppos, 0);
- file_end_write(file);
-
- if (likely(bw == bvec->bv_len))
- return 0;
-
- printk_ratelimited(KERN_ERR
- "loop: Write error at byte offset %llu, length %i.\n",
- (unsigned long long)*ppos, bvec->bv_len);
- if (bw >= 0)
- bw = -EIO;
- return bw;
-}
-
-static int lo_write_simple(struct loop_device *lo, struct request *rq,
- loff_t pos)
-{
- struct bio_vec bvec;
- struct req_iterator iter;
- int ret = 0;
-
- rq_for_each_segment(bvec, rq, iter) {
- ret = lo_write_bvec(lo->lo_backing_file, &bvec, &pos);
- if (ret < 0)
- break;
- cond_resched();
- }
-
- return ret;
-}
-
-/*
- * This is the slow, transforming version that needs to double buffer the
- * data as it cannot do the transformations in place without having direct
- * access to the destination pages of the backing file.
- */
-static int lo_write_transfer(struct loop_device *lo, struct request *rq,
- loff_t pos)
-{
- struct bio_vec bvec, b;
- struct req_iterator iter;
- struct page *page;
- int ret = 0;
-
- page = alloc_page(GFP_NOIO);
- if (unlikely(!page))
- return -ENOMEM;
-
- rq_for_each_segment(bvec, rq, iter) {
- ret = lo_do_transfer(lo, WRITE, page, 0, bvec.bv_page,
- bvec.bv_offset, bvec.bv_len, pos >> 9);
- if (unlikely(ret))
- break;
-
- b.bv_page = page;
- b.bv_offset = 0;
- b.bv_len = bvec.bv_len;
- ret = lo_write_bvec(lo->lo_backing_file, &b, &pos);
- if (ret < 0)
- break;
- }
-
- __free_page(page);
- return ret;
-}
-
-static int lo_read_simple(struct loop_device *lo, struct request *rq,
- loff_t pos)
-{
- struct bio_vec bvec;
- struct req_iterator iter;
- struct iov_iter i;
- ssize_t len;
-
- rq_for_each_segment(bvec, rq, iter) {
- loop_iov_iter_bvec(&i, READ, &bvec, 1, bvec.bv_len);
- len = vfs_iter_read(lo->lo_backing_file, &i, &pos, 0);
- if (len < 0)
- return len;
-
- flush_dcache_page(bvec.bv_page);
-
- if (len != bvec.bv_len) {
- struct bio *bio;
-
- __rq_for_each_bio(bio, rq)
- zero_fill_bio(bio);
- break;
- }
- cond_resched();
- }
-
- return 0;
-}
-
-static int lo_read_transfer(struct loop_device *lo, struct request *rq,
- loff_t pos)
-{
- struct bio_vec bvec, b;
- struct req_iterator iter;
- struct iov_iter i;
- struct page *page;
- ssize_t len;
- int ret = 0;
-
- page = alloc_page(GFP_NOIO);
- if (unlikely(!page))
- return -ENOMEM;
-
- rq_for_each_segment(bvec, rq, iter) {
- loff_t offset = pos;
-
- b.bv_page = page;
- b.bv_offset = 0;
- b.bv_len = bvec.bv_len;
-
- loop_iov_iter_bvec(&i, READ, &b, 1, b.bv_len);
- len = vfs_iter_read(lo->lo_backing_file, &i, &pos, 0);
- if (len < 0) {
- ret = len;
- goto out_free_page;
- }
-
- ret = lo_do_transfer(lo, READ, page, 0, bvec.bv_page,
- bvec.bv_offset, len, offset >> 9);
- if (ret)
- goto out_free_page;
-
- flush_dcache_page(bvec.bv_page);
-
- if (len != bvec.bv_len) {
- struct bio *bio;
-
- __rq_for_each_bio(bio, rq)
- zero_fill_bio(bio);
- break;
- }
- }
-
- ret = 0;
-out_free_page:
- __free_page(page);
- return ret;
-}
-
-static int lo_discard(struct loop_device *lo, struct request *rq, loff_t pos)
-{
- /*
- * We use punch hole to reclaim the free space used by the
- * image a.k.a. discard. However we do not support discard if
- * encryption is enabled, because it may give an attacker
- * useful information.
- */
- struct file *file = lo->lo_backing_file;
- int mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
- int ret;
-
- if ((!file->f_op->fallocate) || lo->lo_encrypt_key_size) {
- ret = -EOPNOTSUPP;
- goto out;
- }
-
- ret = file->f_op->fallocate(file, mode, pos, blk_rq_bytes(rq));
- if (unlikely(ret && ret != -EINVAL && ret != -EOPNOTSUPP))
- ret = -EIO;
- out:
- return ret;
-}
-
-static int lo_req_flush(struct loop_device *lo, struct request *rq)
-{
- struct file *file = lo->lo_backing_file;
- int ret = vfs_fsync(file, 0);
- if (unlikely(ret && ret != -EINVAL))
- ret = -EIO;
-
- return ret;
-}
-
static void lo_complete_rq(struct request *rq)
{
struct loop_cmd *cmd = blk_mq_rq_to_pdu(rq);
@@ -494,133 +282,26 @@ end_io:
}
}
-static void lo_rw_aio_do_completion(struct loop_cmd *cmd)
-{
- struct request *rq = blk_mq_rq_from_pdu(cmd);
-
- if (!atomic_dec_and_test(&cmd->ref))
- return;
- kfree(cmd->bvec);
- cmd->bvec = NULL;
- blk_mq_complete_request(rq);
-}
-
-static void lo_rw_aio_complete(struct kiocb *iocb, long ret, long ret2)
-{
- struct loop_cmd *cmd = container_of(iocb, struct loop_cmd, iocb);
-
- if (cmd->css)
- css_put(cmd->css);
- cmd->ret = ret;
- lo_rw_aio_do_completion(cmd);
-}
-
-static int lo_rw_aio(struct loop_device *lo, struct loop_cmd *cmd,
- loff_t pos, bool rw)
-{
- struct iov_iter iter;
- struct req_iterator rq_iter;
- struct bio_vec *bvec;
- struct request *rq = blk_mq_rq_from_pdu(cmd);
- struct bio *bio = rq->bio;
- struct file *file = lo->lo_backing_file;
- struct bio_vec tmp;
- unsigned int offset;
- int nr_bvec = 0;
- int ret;
-
- rq_for_each_bvec(tmp, rq, rq_iter)
- nr_bvec++;
-
- if (rq->bio != rq->biotail) {
-
- bvec = kmalloc_array(nr_bvec, sizeof(struct bio_vec),
- GFP_NOIO);
- if (!bvec)
- return -EIO;
- cmd->bvec = bvec;
-
- /*
- * The bios of the request may be started from the middle of
- * the 'bvec' because of bio splitting, so we can't directly
- * copy bio->bi_iov_vec to new bvec. The rq_for_each_bvec
- * API will take care of all details for us.
- */
- rq_for_each_bvec(tmp, rq, rq_iter) {
- *bvec = tmp;
- bvec++;
- }
- bvec = cmd->bvec;
- offset = 0;
- } else {
- /*
- * Same here, this bio may be started from the middle of the
- * 'bvec' because of bio splitting, so offset from the bvec
- * must be passed to iov iterator
- */
- offset = bio->bi_iter.bi_bvec_done;
- bvec = __bvec_iter_bvec(bio->bi_io_vec, bio->bi_iter);
- }
- atomic_set(&cmd->ref, 2);
-
- loop_iov_iter_bvec(&iter, rw, bvec, nr_bvec, blk_rq_bytes(rq));
- iter.iov_offset = offset;
-
- cmd->iocb.ki_pos = pos;
- cmd->iocb.ki_filp = file;
- cmd->iocb.ki_complete = lo_rw_aio_complete;
- cmd->iocb.ki_flags = IOCB_DIRECT;
- cmd->iocb.ki_ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_NONE, 0);
- if (cmd->css)
- kthread_associate_blkcg(cmd->css);
-
- if (rw == WRITE)
- ret = call_write_iter(file, &cmd->iocb, &iter);
- else
- ret = call_read_iter(file, &cmd->iocb, &iter);
-
- lo_rw_aio_do_completion(cmd);
- kthread_associate_blkcg(NULL);
-
- if (ret != -EIOCBQUEUED)
- cmd->iocb.ki_complete(&cmd->iocb, ret, 0);
- return 0;
-}
-
static int do_req_filebacked(struct loop_device *lo, struct request *rq)
{
struct loop_cmd *cmd = blk_mq_rq_to_pdu(rq);
- loff_t pos = ((loff_t) blk_rq_pos(rq) << 9) + lo->lo_offset;
- /*
- * lo_write_simple and lo_read_simple should have been covered
- * by io submit style function like lo_rw_aio(), one blocker
- * is that lo_read_simple() need to call flush_dcache_page after
- * the page is written from kernel, and it isn't easy to handle
- * this in io submit style function which submits all segments
- * of the req at one time. And direct read IO doesn't need to
- * run flush_dcache_page().
- */
switch (req_op(rq)) {
case REQ_OP_FLUSH:
- return lo_req_flush(lo, rq);
+ return loop_file_fmt_flush(lo->lo_fmt, rq);
case REQ_OP_DISCARD:
case REQ_OP_WRITE_ZEROES:
- return lo_discard(lo, rq, pos);
+ return loop_file_fmt_discard(lo->lo_fmt, rq);
case REQ_OP_WRITE:
- if (lo->transfer)
- return lo_write_transfer(lo, rq, pos);
- else if (cmd->use_aio)
- return lo_rw_aio(lo, cmd, pos, WRITE);
+ if (cmd->use_aio)
+ return loop_file_fmt_write_aio(lo->lo_fmt, rq);
else
- return lo_write_simple(lo, rq, pos);
+ return loop_file_fmt_write(lo->lo_fmt, rq);
case REQ_OP_READ:
- if (lo->transfer)
- return lo_read_transfer(lo, rq, pos);
- else if (cmd->use_aio)
- return lo_rw_aio(lo, cmd, pos, READ);
+ if (cmd->use_aio)
+ return loop_file_fmt_read_aio(lo->lo_fmt, rq);
else
- return lo_read_simple(lo, rq, pos);
+ return loop_file_fmt_read(lo->lo_fmt, rq);
default:
WARN_ON_ONCE(1);
return -EIO;
@@ -789,31 +470,14 @@ static ssize_t loop_attr_backing_file_show(struct loop_device *lo, char *buf)
return ret;
}
-static ssize_t __print_file_fmt_type(__u32 file_fmt_type, char* buf) {
- switch(file_fmt_type) {
- case LO_FILE_FMT_RAW:
- sprintf(buf, "%s\n", "RAW");
- break;
- case LO_FILE_FMT_QCOW:
- sprintf(buf, "%s\n", "QCOW");
- break;
- case LO_FILE_FMT_VDI:
- sprintf(buf, "%s\n", "VDI");
- break;
- case LO_FILE_FMT_VMDK:
- sprintf(buf, "%s\n", "VMDK");
- break;
- default:
- sprintf(buf, "%s\n", "ERROR: Unsupported loop file format!");
- break;
- }
-
- return strlen(buf);
-}
-
static ssize_t loop_attr_file_fmt_type_show(struct loop_device *lo, char *buf)
{
- return __print_file_fmt_type(lo->lo_file_fmt_type, buf);
+ ssize_t len = 0;
+
+ len = loop_file_fmt_print_type(lo->lo_fmt->file_fmt_type, buf);
+ len += sprintf(buf + len, "\n");
+
+ return len;
}
static ssize_t loop_attr_offset_show(struct loop_device *lo, char *buf)
@@ -1127,6 +791,8 @@ static int __loop_clr_fd(struct loop_device *lo, bool release)
goto out_unlock;
}
+ loop_file_fmt_exit(lo->lo_fmt);
+
/* freeze request queue during the transition */
blk_mq_freeze_queue(lo->lo_queue);
@@ -1267,6 +933,11 @@ loop_set_status(struct loop_device *lo, const struct loop_info64 *info)
err = -EPERM;
goto out_unlock;
}
+ if (lo->lo_state == Lo_bound) {
+ err = loop_file_fmt_init(lo->lo_fmt);
+ if (err)
+ goto out_unlock;
+ }
if (lo->lo_state != Lo_bound) {
err = -ENXIO;
goto out_unlock;
@@ -1308,6 +979,12 @@ loop_set_status(struct loop_device *lo, const struct loop_info64 *info)
if (err)
goto out_unfreeze;
+ if (lo->lo_fmt->file_fmt_type != info->lo_file_fmt_type) {
+ err = loop_file_fmt_change(lo->lo_fmt, info->lo_file_fmt_type);
+ if (err)
+ goto out_unfreeze;
+ }
+
if (lo->lo_offset != info->lo_offset ||
lo->lo_sizelimit != info->lo_sizelimit) {
/* kill_bdev should have truncated all the pages */
@@ -1343,7 +1020,6 @@ loop_set_status(struct loop_device *lo, const struct loop_info64 *info)
lo->lo_encrypt_key_size = info->lo_encrypt_key_size;
lo->lo_init[0] = info->lo_init[0];
lo->lo_init[1] = info->lo_init[1];
- lo->lo_file_fmt_type = info->lo_file_fmt_type;
if (info->lo_encrypt_key_size) {
memcpy(lo->lo_encrypt_key, info->lo_encrypt_key,
info->lo_encrypt_key_size);
@@ -1400,7 +1076,7 @@ loop_get_status(struct loop_device *lo, struct loop_info64 *info)
memcpy(info->lo_encrypt_key, lo->lo_encrypt_key,
lo->lo_encrypt_key_size);
}
- info->lo_file_fmt_type = lo->lo_file_fmt_type;
+ info->lo_file_fmt_type = lo->lo_fmt->file_fmt_type;
/* Drop loop_ctl_mutex while we call into the filesystem. */
path = lo->lo_backing_file->f_path;
@@ -2052,9 +1728,16 @@ static int loop_add(struct loop_device **l, int i)
blk_queue_flag_set(QUEUE_FLAG_NOMERGES, lo->lo_queue);
err = -ENOMEM;
+ lo->lo_fmt = loop_file_fmt_alloc();
+ if (!lo->lo_fmt)
+ goto out_free_queue;
+
+ loop_file_fmt_set_lo(lo->lo_fmt, lo);
+
+ err = -ENOMEM;
disk = lo->lo_disk = alloc_disk(1 << part_shift);
if (!disk)
- goto out_free_queue;
+ goto out_free_file_fmt;
/*
* Disable partition scanning by default. The in-kernel partition
@@ -2090,6 +1773,8 @@ static int loop_add(struct loop_device **l, int i)
*l = lo;
return lo->lo_number;
+out_free_file_fmt:
+ loop_file_fmt_free(lo->lo_fmt);
out_free_queue:
blk_cleanup_queue(lo->lo_queue);
out_cleanup_tags:
@@ -2104,6 +1789,7 @@ out:
static void loop_remove(struct loop_device *lo)
{
+ loop_file_fmt_free(lo->lo_fmt);
del_gendisk(lo->lo_disk);
blk_cleanup_queue(lo->lo_queue);
blk_mq_free_tag_set(&lo->tag_set);