1 // Copyright 2024 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4 5 //go:build freebsd || linux
6 7 package poll
8 9 import "internal/syscall/unix"
10 11 // CopyFileRange copies at most remain bytes of data from src to dst, using
12 // the copy_file_range system call. dst and src must refer to regular files.
13 func CopyFileRange(dst, src *FD, remain int64) (written int64, handled bool, err error) {
14 if !supportCopyFileRange() {
15 return 0, false, nil
16 }
17 18 for remain > 0 {
19 max := min(remain, maxCopyFileRangeRound)
20 n, e := copyFileRange(dst, src, int(max))
21 if n > 0 {
22 remain -= n
23 written += n
24 }
25 handled, err = handleCopyFileRangeErr(e, n, written)
26 if n == 0 || !handled || err != nil {
27 return
28 }
29 }
30 31 return written, true, nil
32 }
33 34 // copyFileRange performs one round of copy_file_range(2).
35 func copyFileRange(dst, src *FD, max int) (written int64, err error) {
36 // For Linux, the signature of copy_file_range(2) is:
37 //
38 // ssize_t copy_file_range(int fd_in, loff_t *off_in,
39 // int fd_out, loff_t *off_out,
40 // size_t len, unsigned int flags);
41 //
42 // For FreeBSD, the signature of copy_file_range(2) is:
43 //
44 // ssize_t
45 // copy_file_range(int infd, off_t *inoffp, int outfd, off_t *outoffp,
46 // size_t len, unsigned int flags);
47 //
48 // Note that in the call to unix.CopyFileRange below, we use nil
49 // values for off_in/off_out and inoffp/outoffp, which means "the file
50 // offset for infd(fd_in) or outfd(fd_out) respectively will be used and
51 // updated by the number of bytes copied".
52 //
53 // That is why we must acquire locks for both file descriptors (and why
54 // this whole machinery is in the internal/poll package to begin with).
55 if err := dst.writeLock(); err != nil {
56 return 0, err
57 }
58 defer dst.writeUnlock()
59 if err := src.readLock(); err != nil {
60 return 0, err
61 }
62 defer src.readUnlock()
63 return ignoringEINTR2(func() (int64, error) {
64 n, err := unix.CopyFileRange(src.Sysfd, nil, dst.Sysfd, nil, max, 0)
65 return int64(n), err
66 })
67 }
68