summaryrefslogtreecommitdiff
path: root/fs/btrfs/send.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/btrfs/send.c')
-rw-r--r--fs/btrfs/send.c193
1 files changed, 113 insertions, 80 deletions
diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
index 6837fe8..e46e0ed 100644
--- a/fs/btrfs/send.c
+++ b/fs/btrfs/send.c
@@ -121,6 +121,7 @@ struct send_ctx {
struct list_head name_cache_list;
int name_cache_size;
+ struct file *cur_inode_filp;
char *read_buf;
};
@@ -564,8 +565,10 @@ static int begin_cmd(struct send_ctx *sctx, int cmd)
{
struct btrfs_cmd_header *hdr;
- if (WARN_ON(!sctx->send_buf))
+ if (!sctx->send_buf) {
+ WARN_ON(1);
return -EINVAL;
+ }
BUG_ON(sctx->send_size);
@@ -788,7 +791,7 @@ static int iterate_inode_ref(struct btrfs_root *root, struct btrfs_path *path,
if (found_key->type == BTRFS_INODE_REF_KEY) {
ptr = (unsigned long)btrfs_item_ptr(eb, slot,
struct btrfs_inode_ref);
- item = btrfs_item_nr(slot);
+ item = btrfs_item_nr(eb, slot);
total = btrfs_item_size(eb, item);
elem_size = sizeof(*iref);
} else {
@@ -902,7 +905,7 @@ static int iterate_dir_item(struct btrfs_root *root, struct btrfs_path *path,
eb = path->nodes[0];
slot = path->slots[0];
- item = btrfs_item_nr(slot);
+ item = btrfs_item_nr(eb, slot);
di = btrfs_item_ptr(eb, slot, struct btrfs_dir_item);
cur = 0;
len = 0;
@@ -2117,6 +2120,77 @@ out:
}
/*
+ * Called for regular files when sending extents data. Opens a struct file
+ * to read from the file.
+ */
+static int open_cur_inode_file(struct send_ctx *sctx)
+{
+ int ret = 0;
+ struct btrfs_key key;
+ struct path path;
+ struct inode *inode;
+ struct dentry *dentry;
+ struct file *filp;
+ int new = 0;
+
+ if (sctx->cur_inode_filp)
+ goto out;
+
+ key.objectid = sctx->cur_ino;
+ key.type = BTRFS_INODE_ITEM_KEY;
+ key.offset = 0;
+
+ inode = btrfs_iget(sctx->send_root->fs_info->sb, &key, sctx->send_root,
+ &new);
+ if (IS_ERR(inode)) {
+ ret = PTR_ERR(inode);
+ goto out;
+ }
+
+ dentry = d_obtain_alias(inode);
+ inode = NULL;
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto out;
+ }
+
+ path.mnt = sctx->mnt;
+ path.dentry = dentry;
+ filp = dentry_open(&path, O_RDONLY | O_LARGEFILE, current_cred());
+ dput(dentry);
+ dentry = NULL;
+ if (IS_ERR(filp)) {
+ ret = PTR_ERR(filp);
+ goto out;
+ }
+ sctx->cur_inode_filp = filp;
+
+out:
+ /*
+ * no xxxput required here as every vfs op
+ * does it by itself on failure
+ */
+ return ret;
+}
+
+/*
+ * Closes the struct file that was created in open_cur_inode_file
+ */
+static int close_cur_inode_file(struct send_ctx *sctx)
+{
+ int ret = 0;
+
+ if (!sctx->cur_inode_filp)
+ goto out;
+
+ ret = filp_close(sctx->cur_inode_filp, NULL);
+ sctx->cur_inode_filp = NULL;
+
+out:
+ return ret;
+}
+
+/*
* Sends a BTRFS_SEND_C_SUBVOL command/item to userspace
*/
static int send_subvol_begin(struct send_ctx *sctx)
@@ -3548,72 +3622,6 @@ out:
return ret;
}
-static ssize_t fill_read_buf(struct send_ctx *sctx, u64 offset, u32 len)
-{
- struct btrfs_root *root = sctx->send_root;
- struct btrfs_fs_info *fs_info = root->fs_info;
- struct inode *inode;
- struct page *page;
- char *addr;
- struct btrfs_key key;
- pgoff_t index = offset >> PAGE_CACHE_SHIFT;
- pgoff_t last_index;
- unsigned pg_offset = offset & ~PAGE_CACHE_MASK;
- ssize_t ret = 0;
-
- key.objectid = sctx->cur_ino;
- key.type = BTRFS_INODE_ITEM_KEY;
- key.offset = 0;
-
- inode = btrfs_iget(fs_info->sb, &key, root, NULL);
- if (IS_ERR(inode))
- return PTR_ERR(inode);
-
- if (offset + len > i_size_read(inode)) {
- if (offset > i_size_read(inode))
- len = 0;
- else
- len = offset - i_size_read(inode);
- }
- if (len == 0)
- goto out;
-
- last_index = (offset + len - 1) >> PAGE_CACHE_SHIFT;
- while (index <= last_index) {
- unsigned cur_len = min_t(unsigned, len,
- PAGE_CACHE_SIZE - pg_offset);
- page = find_or_create_page(inode->i_mapping, index, GFP_NOFS);
- if (!page) {
- ret = -ENOMEM;
- break;
- }
-
- if (!PageUptodate(page)) {
- btrfs_readpage(NULL, page);
- lock_page(page);
- if (!PageUptodate(page)) {
- unlock_page(page);
- page_cache_release(page);
- ret = -EIO;
- break;
- }
- }
-
- addr = kmap(page);
- memcpy(sctx->read_buf + ret, addr + pg_offset, cur_len);
- kunmap(page);
- unlock_page(page);
- page_cache_release(page);
- index++;
- pg_offset = 0;
- len -= cur_len;
- ret += cur_len;
- }
-out:
- iput(inode);
- return ret;
-}
-
/*
* Read some bytes from the current inode/file and send a write command to
* user space.
@@ -3622,20 +3630,35 @@ static int send_write(struct send_ctx *sctx, u64 offset, u32 len)
{
int ret = 0;
struct fs_path *p;
- ssize_t num_read = 0;
+ loff_t pos = offset;
+ int num_read = 0;
+ mm_segment_t old_fs;
p = fs_path_alloc();
if (!p)
return -ENOMEM;
+ /*
+ * vfs normally only accepts user space buffers for security reasons.
+ * we only read from the file and also only provide the read_buf buffer
+ * to vfs. As this buffer does not come from a user space call, it's
+ * ok to temporary allow kernel space buffers.
+ */
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+
verbose_printk("btrfs: send_write offset=%llu, len=%d\n", offset, len);
- num_read = fill_read_buf(sctx, offset, len);
- if (num_read <= 0) {
- if (num_read < 0)
- ret = num_read;
+ ret = open_cur_inode_file(sctx);
+ if (ret < 0)
+ goto out;
+
+ ret = vfs_read(sctx->cur_inode_filp, sctx->read_buf, len, &pos);
+ if (ret < 0)
+ goto out;
+ num_read = ret;
+ if (!num_read)
goto out;
- }
ret = begin_cmd(sctx, BTRFS_SEND_C_WRITE);
if (ret < 0)
@@ -3654,6 +3677,7 @@ verbose_printk("btrfs: send_write offset=%llu, len=%d\n", offset, len);
tlv_put_failure:
out:
fs_path_free(p);
+ set_fs(old_fs);
if (ret < 0)
return ret;
return num_read;
@@ -3902,16 +3926,16 @@ static int is_extent_unchanged(struct send_ctx *sctx,
while (key.offset < ekey->offset + left_len) {
ei = btrfs_item_ptr(eb, slot, struct btrfs_file_extent_item);
right_type = btrfs_file_extent_type(eb, ei);
- if (right_type != BTRFS_FILE_EXTENT_REG) {
- ret = 0;
- goto out;
- }
-
right_disknr = btrfs_file_extent_disk_bytenr(eb, ei);
right_len = btrfs_file_extent_num_bytes(eb, ei);
right_offset = btrfs_file_extent_offset(eb, ei);
right_gen = btrfs_file_extent_generation(eb, ei);
+ if (right_type != BTRFS_FILE_EXTENT_REG) {
+ ret = 0;
+ goto out;
+ }
+
/*
* Are we at extent 8? If yes, we know the extent is changed.
* This may only happen on the first iteration.
@@ -4198,6 +4222,10 @@ static int changed_inode(struct send_ctx *sctx,
u64 left_gen = 0;
u64 right_gen = 0;
+ ret = close_cur_inode_file(sctx);
+ if (ret < 0)
+ goto out;
+
sctx->cur_ino = key->objectid;
sctx->cur_inode_new_gen = 0;
@@ -4658,6 +4686,11 @@ static int send_subvol(struct send_ctx *sctx)
}
out:
+ if (!ret)
+ ret = close_cur_inode_file(sctx);
+ else
+ close_cur_inode_file(sctx);
+
free_recorded_refs(sctx);
return ret;
}