Skip to content

Commit 8622d62

Browse files
committed
Implement FIDUPERANGE for Linux.
This resolves issue #11065 by implementing FIDUPERANGE on Linux.
1 parent 483ccf0 commit 8622d62

File tree

2 files changed

+163
-19
lines changed

2 files changed

+163
-19
lines changed

include/os/linux/zfs/sys/zpl.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ extern int zpl_clone_file_range(struct file *src_file, loff_t src_off,
196196
struct file *dst_file, loff_t dst_off, uint64_t len);
197197
extern int zpl_dedupe_file_range(struct file *src_file, loff_t src_off,
198198
struct file *dst_file, loff_t dst_off, uint64_t len);
199+
extern int zpl_dedupe_file_compare(struct file *src_file, loff_t src_off,
200+
struct file *dst_file, loff_t dst_off, uint64_t len, bool *is_same);
199201

200202
/* compat for FICLONE/FICLONERANGE/FIDEDUPERANGE ioctls */
201203
typedef struct {

module/os/linux/zfs/zpl_file_range.c

Lines changed: 161 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -147,18 +147,35 @@ zpl_remap_file_range(struct file *src_file, loff_t src_off,
147147
* doesn't do that, so we just turn the flag off.
148148
*/
149149
flags &= ~REMAP_FILE_CAN_SHORTEN;
150+
if (flags & REMAP_FILE_DEDUP) {
151+
/* Zero length means to clone everything to the end of the file
152+
*/
153+
if (len == 0)
154+
len = i_size_read(file_inode(src_file)) - src_off;
155+
// Both nodes must be range locked
156+
zfs_locked_range_t *src_zlr = zfs_rangelock_enter(
157+
&ITOZ(file_inode(src_file))->z_rangelock, src_off, len,
158+
RL_READER);
159+
zfs_locked_range_t *dst_zlr = zfs_rangelock_enter(
160+
&ITOZ(file_inode(dst_file))->z_rangelock, dst_off, len,
161+
RL_WRITER);
162+
bool same = false;
163+
int ret = zpl_dedupe_file_compare(src_file, src_off, dst_file,
164+
dst_off, len, &same);
165+
if (ret)
166+
return ret;
167+
if (!same)
168+
return -EBADE;
169+
/* TODO(locked version) */
170+
ret = __zpl_clone_file_range(src_file, src_off, dst_file,
171+
dst_off, len);
172+
zfs_rangelock_exit(src_zlr);
173+
zfs_rangelock_exit(dst_zlr);
174+
}
150175

151-
if (flags & REMAP_FILE_DEDUP)
152-
/* No support for dedup yet */
153-
return (-EOPNOTSUPP);
154-
155-
/* Zero length means to clone everything to the end of the file */
156-
if (len == 0)
157-
len = i_size_read(file_inode(src_file)) - src_off;
158-
159-
return (__zpl_clone_file_range(src_file, src_off,
160-
dst_file, dst_off, len));
161-
}
176+
return (__zpl_clone_file_range(src_file, src_off, dst_file,
177+
dst_off, len));
178+
}
162179
#endif /* HAVE_VFS_REMAP_FILE_RANGE */
163180

164181
#if defined(HAVE_VFS_CLONE_FILE_RANGE) || \
@@ -202,9 +219,10 @@ zpl_ioctl_ficlone(struct file *dst_file, void *arg)
202219
if (src_file == NULL)
203220
return (-EBADF);
204221

205-
if (dst_file->f_op != src_file->f_op)
222+
if (dst_file->f_op != src_file->f_op) {
223+
fput(src_file);
206224
return (-EXDEV);
207-
225+
}
208226
size_t len = i_size_read(file_inode(src_file));
209227

210228
ssize_t ret =
@@ -237,8 +255,10 @@ zpl_ioctl_ficlonerange(struct file *dst_file, void __user *arg)
237255
if (src_file == NULL)
238256
return (-EBADF);
239257

240-
if (dst_file->f_op != src_file->f_op)
258+
if (dst_file->f_op != src_file->f_op) {
259+
fput(src_file);
241260
return (-EXDEV);
261+
}
242262

243263
size_t len = fcr.fcr_src_length;
244264
if (len == 0)
@@ -263,10 +283,132 @@ zpl_ioctl_ficlonerange(struct file *dst_file, void __user *arg)
263283

264284
/* Entry point for FIDEDUPERANGE, before Linux 4.5. */
265285
long
266-
zpl_ioctl_fideduperange(struct file *filp, void *arg)
267-
{
268-
(void) arg;
286+
zpl_ioctl_fideduperange(struct file *src_file, void *arg) {
287+
zfs_ioc_compat_dedupe_range_t dup;
288+
int i;
269289

270-
/* No support for dedup yet */
271-
return (-ENOTTY);
290+
if (copy_from_user(&dup, arg, sizeof(dup)))
291+
return (-EFAULT);
292+
293+
u16 count = dup.fdr_dest_count;
294+
struct inode *src_inode = file_inode(src_file);
295+
296+
/* Nothing to duplicate to */
297+
if (count == 0)
298+
return -EINVAL;
299+
300+
/* Check the src file */
301+
if (!(src_file->f_mode & FMODE_READ))
302+
return -EINVAL;
303+
304+
if (S_ISDIR(src_inode->i_mode))
305+
return -EISDIR;
306+
307+
if (!S_ISREG(src_inode->i_mode))
308+
return -EINVAL;
309+
310+
if (dup.fdr_src_offset + dup.fdr_src_length > i_size_read(src_inode))
311+
return -EINVAL;
312+
313+
/* Check the dup structure */
314+
if (dup.fdr_reserved1 || dup.fdr_reserved2)
315+
return -EINVAL;
316+
317+
/* Set output values to safe results */
318+
for (i = 0; i < count; i++) {
319+
dup.fdr_info[i].fdri_bytes_deduped = 0ULL;
320+
dup.fdr_info[i].fdri_status = FILE_DEDUPE_RANGE_SAME;
321+
}
322+
323+
for (i = 0; i < count; i++) {
324+
struct fd dst_fd = fdget(dup.fdr_info[i].fdri_dest_fd);
325+
struct file *dst_file = dst_fd.file;
326+
327+
if (!dst_file) {
328+
dup.fdr_info[i].fdri_status = -EBADF;
329+
continue;
330+
}
331+
if (dup.fdr_info[i].fdri_reserved) {
332+
dup.fdr_info[i].fdri_status = -EINVAL;
333+
goto do_fdput;
334+
}
335+
loff_t deduped =
336+
zpl_remap_file_range(src_file, dup.fdr_src_offset, dst_file,
337+
dup.fdr_info[i].fdri_dest_offset,
338+
dup.fdr_src_length, REMAP_FILE_DEDUP);
339+
if (deduped == -EBADE) {
340+
dup.fdr_info[i].fdri_status = FILE_DEDUPE_RANGE_DIFFERS;
341+
} else if (deduped < 0) {
342+
dup.fdr_info[i].fdri_status = deduped;
343+
} else {
344+
dup.fdr_info[i].fdri_bytes_deduped = dup.fdr_src_length;
345+
}
346+
do_fdput:
347+
fdput(dst_fd);
348+
}
349+
return 0;
350+
}
351+
352+
int
353+
zpl_dedupe_file_compare(struct file *src_file, loff_t src_off,
354+
struct file *dst_file, loff_t dst_off, uint64_t len,
355+
bool *is_same) {
356+
bool same = true;
357+
int err = 0;
358+
znode_t *src_znode = ITOZ(file_inode(src_file));
359+
znode_t *dst_znode = ITOZ(file_inode(dst_file));
360+
fstrans_cookie_t cookie;
361+
cred_t *cr = CRED();
362+
void *src_buf = kmem_zalloc(PAGE_SIZE, KM_SLEEP);
363+
void *dst_buf = kmem_zalloc(PAGE_SIZE, KM_SLEEP);
364+
365+
366+
while (len) {
367+
zfs_uio_t uio;
368+
369+
uint64_t cmp_len = min(((uint64_t)PAGE_SIZE), len);
370+
371+
if (cmp_len == 0)
372+
break;
373+
struct iovec iov;
374+
iov.iov_base = src_buf;
375+
iov.iov_len = cmp_len;
376+
377+
zfs_uio_iovec_init(&uio, &iov, 1, 0, UIO_SYSSPACE, cmp_len, 0);
378+
crhold(cr);
379+
cookie = spl_fstrans_mark();
380+
err = -zfs_read(src_znode, &uio, src_file->f_flags, cr);
381+
spl_fstrans_unmark(cookie);
382+
crfree(cr);
383+
384+
if (err)
385+
goto done;
386+
iov.iov_base = dst_buf;
387+
iov.iov_len = cmp_len;
388+
crhold(cr);
389+
cookie = spl_fstrans_mark();
390+
err = -zfs_read(dst_znode, &uio, dst_file->f_flags, cr);
391+
spl_fstrans_unmark(cookie);
392+
crfree(cr);
393+
394+
if (err)
395+
goto done;
396+
397+
if (memcmp(src_buf, dst_buf, cmp_len))
398+
same = false;
399+
400+
if (!same)
401+
break;
402+
403+
src_off += cmp_len;
404+
dst_off += cmp_len;
405+
len -= cmp_len;
406+
}
407+
408+
*is_same = same;
409+
410+
done:
411+
kmem_free(src_buf, PAGE_SIZE);
412+
kmem_free(dst_buf, PAGE_SIZE);
413+
return err;
272414
}

0 commit comments

Comments
 (0)