xref: /freebsd/tests/sys/kern/copy_file_range.c (revision 3af6f55735cef7df72ca3f4ecf2b0027abb5fcb8)
1 /*
2  * Copyright (c) 2025 Mark Johnston <markj@FreeBSD.org>
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  */
6 
7 #include <sys/mman.h>
8 #include <sys/stat.h>
9 
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <limits.h>
13 #include <stdint.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <unistd.h>
17 
18 #include <atf-c.h>
19 #include <sha256.h>
20 
21 /*
22  * Create a file with random data and size between 1B and 32MB.  Return a file
23  * descriptor for the file.
24  */
25 static int
genfile(void)26 genfile(void)
27 {
28 	char buf[256], file[NAME_MAX];
29 	size_t sz;
30 	int fd;
31 
32 	sz = (random() % (32 * 1024 * 1024ul)) + 1;
33 
34 	snprintf(file, sizeof(file), "testfile.XXXXXX");
35 	fd = mkstemp(file);
36 	ATF_REQUIRE(fd != -1);
37 
38 	while (sz > 0) {
39 		ssize_t n;
40 		int error;
41 
42 		error = getentropy(buf, sizeof(buf));
43 		ATF_REQUIRE(error == 0);
44 		n = write(fd, buf, sizeof(buf) < sz ? sizeof(buf) : sz);
45 		ATF_REQUIRE(n > 0);
46 
47 		sz -= n;
48 	}
49 
50 	ATF_REQUIRE(lseek(fd, 0, SEEK_SET) == 0);
51 	return (fd);
52 }
53 
54 /*
55  * Return true if the file data in the two file descriptors is the same,
56  * false otherwise.
57  */
58 static bool
cmpfile(int fd1,int fd2)59 cmpfile(int fd1, int fd2)
60 {
61 	struct stat st1, st2;
62 	void *addr1, *addr2;
63 	size_t sz;
64 	int res;
65 
66 	ATF_REQUIRE(fstat(fd1, &st1) == 0);
67 	ATF_REQUIRE(fstat(fd2, &st2) == 0);
68 	if (st1.st_size != st2.st_size)
69 		return (false);
70 
71 	sz = st1.st_size;
72 	addr1 = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd1, 0);
73 	ATF_REQUIRE(addr1 != MAP_FAILED);
74 	addr2 = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd2, 0);
75 	ATF_REQUIRE(addr2 != MAP_FAILED);
76 
77 	res = memcmp(addr1, addr2, sz);
78 
79 	ATF_REQUIRE(munmap(addr1, sz) == 0);
80 	ATF_REQUIRE(munmap(addr2, sz) == 0);
81 
82 	return (res == 0);
83 }
84 
85 /*
86  * Exercise a few error paths in the copy_file_range() syscall.
87  */
88 ATF_TC_WITHOUT_HEAD(copy_file_range_invalid);
ATF_TC_BODY(copy_file_range_invalid,tc)89 ATF_TC_BODY(copy_file_range_invalid, tc)
90 {
91 	off_t off1, off2;
92 	int fd1, fd2;
93 
94 	fd1 = genfile();
95 	fd2 = genfile();
96 
97 	/* Can't copy a file to itself without explicit offsets. */
98 	ATF_REQUIRE_ERRNO(EINVAL,
99 	    copy_file_range(fd1, NULL, fd1, NULL, SSIZE_MAX, 0) == -1);
100 
101 	/* When copying a file to itself, ranges cannot overlap. */
102 	off1 = off2 = 0;
103 	ATF_REQUIRE_ERRNO(EINVAL,
104 	    copy_file_range(fd1, &off1, fd1, &off2, 1, 0) == -1);
105 
106 	/* Negative offsets are not allowed. */
107 	off1 = -1;
108 	off2 = 0;
109 	ATF_REQUIRE_ERRNO(EINVAL,
110 	    copy_file_range(fd1, &off1, fd2, &off2, 42, 0) == -1);
111 	ATF_REQUIRE_ERRNO(EINVAL,
112 	    copy_file_range(fd2, &off2, fd1, &off1, 42, 0) == -1);
113 }
114 
115 /*
116  * Make sure that copy_file_range() updates the file offsets passed to it.
117  */
118 ATF_TC_WITHOUT_HEAD(copy_file_range_offset);
ATF_TC_BODY(copy_file_range_offset,tc)119 ATF_TC_BODY(copy_file_range_offset, tc)
120 {
121 	struct stat sb;
122 	off_t off1, off2;
123 	ssize_t n;
124 	int fd1, fd2;
125 
126 	off1 = off2 = 0;
127 
128 	fd1 = genfile();
129 	fd2 = open("copy", O_RDWR | O_CREAT, 0644);
130 	ATF_REQUIRE(fd2 != -1);
131 
132 	ATF_REQUIRE(fstat(fd1, &sb) == 0);
133 
134 	ATF_REQUIRE(lseek(fd1, 0, SEEK_CUR) == 0);
135 	ATF_REQUIRE(lseek(fd2, 0, SEEK_CUR) == 0);
136 
137 	do {
138 		off_t ooff1, ooff2;
139 
140 		ooff1 = off1;
141 		ooff2 = off2;
142 		n = copy_file_range(fd1, &off1, fd2, &off2, sb.st_size, 0);
143 		ATF_REQUIRE(n >= 0);
144 		ATF_REQUIRE_EQ(off1, ooff1 + n);
145 		ATF_REQUIRE_EQ(off2, ooff2 + n);
146 	} while (n != 0);
147 
148 	/* Offsets should have been adjusted by copy_file_range(). */
149 	ATF_REQUIRE_EQ(off1, sb.st_size);
150 	ATF_REQUIRE_EQ(off2, sb.st_size);
151 	/* Seek offsets should have been left alone. */
152 	ATF_REQUIRE(lseek(fd1, 0, SEEK_CUR) == 0);
153 	ATF_REQUIRE(lseek(fd2, 0, SEEK_CUR) == 0);
154 	/* Make sure the file contents are the same. */
155 	ATF_REQUIRE_MSG(cmpfile(fd1, fd2), "file contents differ");
156 
157 	ATF_REQUIRE(close(fd1) == 0);
158 	ATF_REQUIRE(close(fd2) == 0);
159 }
160 
161 /*
162  * Make sure that copying to a larger file doesn't cause it to be truncated.
163  */
164 ATF_TC_WITHOUT_HEAD(copy_file_range_truncate);
ATF_TC_BODY(copy_file_range_truncate,tc)165 ATF_TC_BODY(copy_file_range_truncate, tc)
166 {
167 	struct stat sb, sb1, sb2;
168 	char digest1[65], digest2[65];
169 	off_t off;
170 	ssize_t n;
171 	int fd1, fd2;
172 
173 	fd1 = genfile();
174 	fd2 = genfile();
175 
176 	ATF_REQUIRE(fstat(fd1, &sb1) == 0);
177 	ATF_REQUIRE(fstat(fd2, &sb2) == 0);
178 
179 	/* fd1 refers to the smaller file. */
180 	if (sb1.st_size > sb2.st_size) {
181 		int tmp;
182 
183 		tmp = fd1;
184 		fd1 = fd2;
185 		fd2 = tmp;
186 		ATF_REQUIRE(fstat(fd1, &sb1) == 0);
187 		ATF_REQUIRE(fstat(fd2, &sb2) == 0);
188 	}
189 
190 	/*
191 	 * Compute a hash of the bytes in the larger file which lie beyond the
192 	 * length of the smaller file.
193 	 */
194 	SHA256_FdChunk(fd2, digest1, sb1.st_size, sb2.st_size - sb1.st_size);
195 	ATF_REQUIRE(lseek(fd2, 0, SEEK_SET) == 0);
196 
197 	do {
198 		n = copy_file_range(fd1, NULL, fd2, NULL, SSIZE_MAX, 0);
199 		ATF_REQUIRE(n >= 0);
200 	} while (n != 0);
201 
202 	/* Validate file offsets after the copy. */
203 	off = lseek(fd1, 0, SEEK_CUR);
204 	ATF_REQUIRE(off == sb1.st_size);
205 	off = lseek(fd2, 0, SEEK_CUR);
206 	ATF_REQUIRE(off == sb1.st_size);
207 
208 	/* The larger file's size should remain the same. */
209 	ATF_REQUIRE(fstat(fd2, &sb) == 0);
210 	ATF_REQUIRE(sb.st_size == sb2.st_size);
211 
212 	/* The bytes beyond the end of the copy should be unchanged. */
213 	SHA256_FdChunk(fd2, digest2, sb1.st_size, sb2.st_size - sb1.st_size);
214 	ATF_REQUIRE_MSG(strcmp(digest1, digest2) == 0,
215 	    "trailing file contents differ after copy_file_range()");
216 
217 	/*
218 	 * Verify that the copy actually replicated bytes from the smaller file.
219 	 */
220 	ATF_REQUIRE(ftruncate(fd2, sb1.st_size) == 0);
221 	ATF_REQUIRE(cmpfile(fd1, fd2));
222 }
223 
ATF_TP_ADD_TCS(tp)224 ATF_TP_ADD_TCS(tp)
225 {
226 	ATF_TP_ADD_TC(tp, copy_file_range_invalid);
227 	ATF_TP_ADD_TC(tp, copy_file_range_offset);
228 	ATF_TP_ADD_TC(tp, copy_file_range_truncate);
229 
230 	return (atf_no_error());
231 }
232