|
16 | 16 | #include <linux/pagemap.h>
|
17 | 17 | #include <linux/splice.h>
|
18 | 18 | #include <linux/compat.h>
|
| 19 | +#include <linux/mount.h> |
19 | 20 | #include "internal.h"
|
20 | 21 |
|
21 | 22 | #include <asm/uaccess.h>
|
@@ -1327,3 +1328,122 @@ COMPAT_SYSCALL_DEFINE4(sendfile64, int, out_fd, int, in_fd,
|
1327 | 1328 | return do_sendfile(out_fd, in_fd, NULL, count, 0);
|
1328 | 1329 | }
|
1329 | 1330 | #endif
|
| 1331 | + |
| 1332 | +/* |
| 1333 | + * copy_file_range() differs from regular file read and write in that it |
| 1334 | + * specifically allows return partial success. When it does so is up to |
| 1335 | + * the copy_file_range method. |
| 1336 | + */ |
| 1337 | +ssize_t vfs_copy_file_range(struct file *file_in, loff_t pos_in, |
| 1338 | + struct file *file_out, loff_t pos_out, |
| 1339 | + size_t len, unsigned int flags) |
| 1340 | +{ |
| 1341 | + struct inode *inode_in = file_inode(file_in); |
| 1342 | + struct inode *inode_out = file_inode(file_out); |
| 1343 | + ssize_t ret; |
| 1344 | + |
| 1345 | + if (flags != 0) |
| 1346 | + return -EINVAL; |
| 1347 | + |
| 1348 | + /* copy_file_range allows full ssize_t len, ignoring MAX_RW_COUNT */ |
| 1349 | + ret = rw_verify_area(READ, file_in, &pos_in, len); |
| 1350 | + if (ret >= 0) |
| 1351 | + ret = rw_verify_area(WRITE, file_out, &pos_out, len); |
| 1352 | + if (ret < 0) |
| 1353 | + return ret; |
| 1354 | + |
| 1355 | + if (!(file_in->f_mode & FMODE_READ) || |
| 1356 | + !(file_out->f_mode & FMODE_WRITE) || |
| 1357 | + (file_out->f_flags & O_APPEND) || |
| 1358 | + !file_out->f_op->copy_file_range) |
| 1359 | + return -EBADF; |
| 1360 | + |
| 1361 | + /* this could be relaxed once a method supports cross-fs copies */ |
| 1362 | + if (inode_in->i_sb != inode_out->i_sb) |
| 1363 | + return -EXDEV; |
| 1364 | + |
| 1365 | + if (len == 0) |
| 1366 | + return 0; |
| 1367 | + |
| 1368 | + ret = mnt_want_write_file(file_out); |
| 1369 | + if (ret) |
| 1370 | + return ret; |
| 1371 | + |
| 1372 | + ret = file_out->f_op->copy_file_range(file_in, pos_in, file_out, pos_out, |
| 1373 | + len, flags); |
| 1374 | + if (ret > 0) { |
| 1375 | + fsnotify_access(file_in); |
| 1376 | + add_rchar(current, ret); |
| 1377 | + fsnotify_modify(file_out); |
| 1378 | + add_wchar(current, ret); |
| 1379 | + } |
| 1380 | + inc_syscr(current); |
| 1381 | + inc_syscw(current); |
| 1382 | + |
| 1383 | + mnt_drop_write_file(file_out); |
| 1384 | + |
| 1385 | + return ret; |
| 1386 | +} |
| 1387 | +EXPORT_SYMBOL(vfs_copy_file_range); |
| 1388 | + |
| 1389 | +SYSCALL_DEFINE6(copy_file_range, int, fd_in, loff_t __user *, off_in, |
| 1390 | + int, fd_out, loff_t __user *, off_out, |
| 1391 | + size_t, len, unsigned int, flags) |
| 1392 | +{ |
| 1393 | + loff_t pos_in; |
| 1394 | + loff_t pos_out; |
| 1395 | + struct fd f_in; |
| 1396 | + struct fd f_out; |
| 1397 | + ssize_t ret = -EBADF; |
| 1398 | + |
| 1399 | + f_in = fdget(fd_in); |
| 1400 | + if (!f_in.file) |
| 1401 | + goto out2; |
| 1402 | + |
| 1403 | + f_out = fdget(fd_out); |
| 1404 | + if (!f_out.file) |
| 1405 | + goto out1; |
| 1406 | + |
| 1407 | + ret = -EFAULT; |
| 1408 | + if (off_in) { |
| 1409 | + if (copy_from_user(&pos_in, off_in, sizeof(loff_t))) |
| 1410 | + goto out; |
| 1411 | + } else { |
| 1412 | + pos_in = f_in.file->f_pos; |
| 1413 | + } |
| 1414 | + |
| 1415 | + if (off_out) { |
| 1416 | + if (copy_from_user(&pos_out, off_out, sizeof(loff_t))) |
| 1417 | + goto out; |
| 1418 | + } else { |
| 1419 | + pos_out = f_out.file->f_pos; |
| 1420 | + } |
| 1421 | + |
| 1422 | + ret = vfs_copy_file_range(f_in.file, pos_in, f_out.file, pos_out, len, |
| 1423 | + flags); |
| 1424 | + if (ret > 0) { |
| 1425 | + pos_in += ret; |
| 1426 | + pos_out += ret; |
| 1427 | + |
| 1428 | + if (off_in) { |
| 1429 | + if (copy_to_user(off_in, &pos_in, sizeof(loff_t))) |
| 1430 | + ret = -EFAULT; |
| 1431 | + } else { |
| 1432 | + f_in.file->f_pos = pos_in; |
| 1433 | + } |
| 1434 | + |
| 1435 | + if (off_out) { |
| 1436 | + if (copy_to_user(off_out, &pos_out, sizeof(loff_t))) |
| 1437 | + ret = -EFAULT; |
| 1438 | + } else { |
| 1439 | + f_out.file->f_pos = pos_out; |
| 1440 | + } |
| 1441 | + } |
| 1442 | + |
| 1443 | +out: |
| 1444 | + fdput(f_out); |
| 1445 | +out1: |
| 1446 | + fdput(f_in); |
| 1447 | +out2: |
| 1448 | + return ret; |
| 1449 | +} |
0 commit comments