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 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 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); 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); 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); 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 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