|
@@ -977,6 +977,146 @@ out_unlock:
|
|
|
return error;
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * Flush all file writes out to disk.
|
|
|
+ */
|
|
|
+static int
|
|
|
+xfs_file_wait_for_io(
|
|
|
+ struct inode *inode,
|
|
|
+ loff_t offset,
|
|
|
+ size_t len)
|
|
|
+{
|
|
|
+ loff_t rounding;
|
|
|
+ loff_t ioffset;
|
|
|
+ loff_t iendoffset;
|
|
|
+ loff_t bs;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ bs = inode->i_sb->s_blocksize;
|
|
|
+ inode_dio_wait(inode);
|
|
|
+
|
|
|
+ rounding = max_t(xfs_off_t, bs, PAGE_SIZE);
|
|
|
+ ioffset = round_down(offset, rounding);
|
|
|
+ iendoffset = round_up(offset + len, rounding) - 1;
|
|
|
+ ret = filemap_write_and_wait_range(inode->i_mapping, ioffset,
|
|
|
+ iendoffset);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/* Hook up to the VFS reflink function */
|
|
|
+STATIC int
|
|
|
+xfs_file_share_range(
|
|
|
+ struct file *file_in,
|
|
|
+ loff_t pos_in,
|
|
|
+ struct file *file_out,
|
|
|
+ loff_t pos_out,
|
|
|
+ u64 len)
|
|
|
+{
|
|
|
+ struct inode *inode_in;
|
|
|
+ struct inode *inode_out;
|
|
|
+ ssize_t ret;
|
|
|
+ loff_t bs;
|
|
|
+ loff_t isize;
|
|
|
+ int same_inode;
|
|
|
+ loff_t blen;
|
|
|
+
|
|
|
+ inode_in = file_inode(file_in);
|
|
|
+ inode_out = file_inode(file_out);
|
|
|
+ bs = inode_out->i_sb->s_blocksize;
|
|
|
+
|
|
|
+ /* Don't touch certain kinds of inodes */
|
|
|
+ if (IS_IMMUTABLE(inode_out))
|
|
|
+ return -EPERM;
|
|
|
+ if (IS_SWAPFILE(inode_in) ||
|
|
|
+ IS_SWAPFILE(inode_out))
|
|
|
+ return -ETXTBSY;
|
|
|
+
|
|
|
+ /* Reflink only works within this filesystem. */
|
|
|
+ if (inode_in->i_sb != inode_out->i_sb)
|
|
|
+ return -EXDEV;
|
|
|
+ same_inode = (inode_in->i_ino == inode_out->i_ino);
|
|
|
+
|
|
|
+ /* Don't reflink dirs, pipes, sockets... */
|
|
|
+ if (S_ISDIR(inode_in->i_mode) || S_ISDIR(inode_out->i_mode))
|
|
|
+ return -EISDIR;
|
|
|
+ if (S_ISFIFO(inode_in->i_mode) || S_ISFIFO(inode_out->i_mode))
|
|
|
+ return -EINVAL;
|
|
|
+ if (!S_ISREG(inode_in->i_mode) || !S_ISREG(inode_out->i_mode))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ /* Are we going all the way to the end? */
|
|
|
+ isize = i_size_read(inode_in);
|
|
|
+ if (isize == 0)
|
|
|
+ return 0;
|
|
|
+ if (len == 0)
|
|
|
+ len = isize - pos_in;
|
|
|
+
|
|
|
+ /* Ensure offsets don't wrap and the input is inside i_size */
|
|
|
+ if (pos_in + len < pos_in || pos_out + len < pos_out ||
|
|
|
+ pos_in + len > isize)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ /* If we're linking to EOF, continue to the block boundary. */
|
|
|
+ if (pos_in + len == isize)
|
|
|
+ blen = ALIGN(isize, bs) - pos_in;
|
|
|
+ else
|
|
|
+ blen = len;
|
|
|
+
|
|
|
+ /* Only reflink if we're aligned to block boundaries */
|
|
|
+ if (!IS_ALIGNED(pos_in, bs) || !IS_ALIGNED(pos_in + blen, bs) ||
|
|
|
+ !IS_ALIGNED(pos_out, bs) || !IS_ALIGNED(pos_out + blen, bs))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ /* Don't allow overlapped reflink within the same file */
|
|
|
+ if (same_inode && pos_out + blen > pos_in && pos_out < pos_in + blen)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ /* Wait for the completion of any pending IOs on srcfile */
|
|
|
+ ret = xfs_file_wait_for_io(inode_in, pos_in, len);
|
|
|
+ if (ret)
|
|
|
+ goto out_unlock;
|
|
|
+ ret = xfs_file_wait_for_io(inode_out, pos_out, len);
|
|
|
+ if (ret)
|
|
|
+ goto out_unlock;
|
|
|
+
|
|
|
+ ret = xfs_reflink_remap_range(XFS_I(inode_in), pos_in, XFS_I(inode_out),
|
|
|
+ pos_out, len);
|
|
|
+ if (ret < 0)
|
|
|
+ goto out_unlock;
|
|
|
+
|
|
|
+out_unlock:
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+STATIC ssize_t
|
|
|
+xfs_file_copy_range(
|
|
|
+ struct file *file_in,
|
|
|
+ loff_t pos_in,
|
|
|
+ struct file *file_out,
|
|
|
+ loff_t pos_out,
|
|
|
+ size_t len,
|
|
|
+ unsigned int flags)
|
|
|
+{
|
|
|
+ int error;
|
|
|
+
|
|
|
+ error = xfs_file_share_range(file_in, pos_in, file_out, pos_out,
|
|
|
+ len);
|
|
|
+ if (error)
|
|
|
+ return error;
|
|
|
+ return len;
|
|
|
+}
|
|
|
+
|
|
|
+STATIC int
|
|
|
+xfs_file_clone_range(
|
|
|
+ struct file *file_in,
|
|
|
+ loff_t pos_in,
|
|
|
+ struct file *file_out,
|
|
|
+ loff_t pos_out,
|
|
|
+ u64 len)
|
|
|
+{
|
|
|
+ return xfs_file_share_range(file_in, pos_in, file_out, pos_out,
|
|
|
+ len);
|
|
|
+}
|
|
|
|
|
|
STATIC int
|
|
|
xfs_file_open(
|
|
@@ -1637,6 +1777,8 @@ const struct file_operations xfs_file_operations = {
|
|
|
.release = xfs_file_release,
|
|
|
.fsync = xfs_file_fsync,
|
|
|
.fallocate = xfs_file_fallocate,
|
|
|
+ .copy_file_range = xfs_file_copy_range,
|
|
|
+ .clone_file_range = xfs_file_clone_range,
|
|
|
};
|
|
|
|
|
|
const struct file_operations xfs_dir_file_operations = {
|