1 | /* Tests for copy_file_range. |
2 | Copyright (C) 2017-2022 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | #include <array_length.h> |
20 | #include <errno.h> |
21 | #include <fcntl.h> |
22 | #include <inttypes.h> |
23 | #include <stdbool.h> |
24 | #include <stdio.h> |
25 | #include <stdlib.h> |
26 | #include <string.h> |
27 | #include <support/check.h> |
28 | #include <support/support.h> |
29 | #include <support/temp_file.h> |
30 | #include <support/test-driver.h> |
31 | #include <support/xunistd.h> |
32 | |
33 | /* Boolean flags which indicate whether to use pointers with explicit |
34 | output flags. */ |
35 | static int do_inoff; |
36 | static int do_outoff; |
37 | |
38 | /* Name and descriptors of the input files. Files are truncated and |
39 | reopened (with O_RDWR) between tests. */ |
40 | static char *infile; |
41 | static int infd; |
42 | static char *outfile; |
43 | static int outfd; |
44 | |
45 | /* Input and output offsets. Set according to do_inoff and do_outoff |
46 | before the test. The offsets themselves are always set to |
47 | zero. */ |
48 | static off64_t inoff; |
49 | static off64_t *pinoff; |
50 | static off64_t outoff; |
51 | static off64_t *poutoff; |
52 | |
53 | /* These are a collection of copy sizes used in tests. */ |
54 | enum { maximum_size = 99999 }; |
55 | static const int typical_sizes[] = |
56 | { 0, 1, 2, 3, 1024, 2048, 4096, 8191, 8192, 8193, maximum_size }; |
57 | |
58 | /* The random contents of this array can be used as a pattern to check |
59 | for correct write operations. */ |
60 | static unsigned char random_data[maximum_size]; |
61 | |
62 | /* The size chosen by the test harness. */ |
63 | static int current_size; |
64 | |
65 | /* Perform a copy of a file. */ |
66 | static void |
67 | simple_file_copy (void) |
68 | { |
69 | xwrite (infd, random_data, current_size); |
70 | |
71 | int length; |
72 | int in_skipped; /* Expected skipped bytes in input. */ |
73 | if (do_inoff) |
74 | { |
75 | xlseek (fd: infd, offset: 1, SEEK_SET); |
76 | inoff = 2; |
77 | length = current_size - 3; |
78 | in_skipped = 2; |
79 | } |
80 | else |
81 | { |
82 | xlseek (fd: infd, offset: 3, SEEK_SET); |
83 | length = current_size - 5; |
84 | in_skipped = 3; |
85 | } |
86 | int out_skipped; /* Expected skipped bytes before the written data. */ |
87 | if (do_outoff) |
88 | { |
89 | xlseek (fd: outfd, offset: 4, SEEK_SET); |
90 | outoff = 5; |
91 | out_skipped = 5; |
92 | } |
93 | else |
94 | { |
95 | xlseek (fd: outfd, offset: 6, SEEK_SET); |
96 | length = current_size - 6; |
97 | out_skipped = 6; |
98 | } |
99 | if (length < 0) |
100 | length = 0; |
101 | |
102 | TEST_COMPARE (copy_file_range (infd, pinoff, outfd, poutoff, |
103 | length, 0), length); |
104 | if (do_inoff) |
105 | { |
106 | TEST_COMPARE (inoff, 2 + length); |
107 | TEST_COMPARE (xlseek (infd, 0, SEEK_CUR), 1); |
108 | } |
109 | else |
110 | TEST_COMPARE (xlseek (infd, 0, SEEK_CUR), 3 + length); |
111 | if (do_outoff) |
112 | { |
113 | TEST_COMPARE (outoff, 5 + length); |
114 | TEST_COMPARE (xlseek (outfd, 0, SEEK_CUR), 4); |
115 | } |
116 | else |
117 | TEST_COMPARE (xlseek (outfd, 0, SEEK_CUR), 6 + length); |
118 | |
119 | struct stat64 st; |
120 | xfstat (fd: outfd, &st); |
121 | if (length > 0) |
122 | TEST_COMPARE (st.st_size, out_skipped + length); |
123 | else |
124 | { |
125 | /* If we did not write anything, we also did not add any |
126 | padding. */ |
127 | TEST_COMPARE (st.st_size, 0); |
128 | return; |
129 | } |
130 | |
131 | xlseek (fd: outfd, offset: 0, SEEK_SET); |
132 | char *bytes = xmalloc (n: st.st_size); |
133 | TEST_COMPARE (read (outfd, bytes, st.st_size), st.st_size); |
134 | for (int i = 0; i < out_skipped; ++i) |
135 | TEST_COMPARE (bytes[i], 0); |
136 | TEST_VERIFY (memcmp (bytes + out_skipped, random_data + in_skipped, |
137 | length) == 0); |
138 | free (ptr: bytes); |
139 | } |
140 | |
141 | /* Test that a short input file results in a shortened copy. */ |
142 | static void |
143 | short_copy (void) |
144 | { |
145 | if (current_size == 0) |
146 | /* Nothing to shorten. */ |
147 | return; |
148 | |
149 | /* Two subtests, one with offset 0 and current_size - 1 bytes, and |
150 | another one with current_size bytes, but offset 1. */ |
151 | for (int shift = 0; shift < 2; ++shift) |
152 | { |
153 | if (test_verbose > 0) |
154 | printf (format: "info: shift=%d\n" , shift); |
155 | xftruncate (fd: infd, length: 0); |
156 | xlseek (fd: infd, offset: 0, SEEK_SET); |
157 | xwrite (infd, random_data, current_size - !shift); |
158 | |
159 | if (do_inoff) |
160 | { |
161 | inoff = shift; |
162 | xlseek (fd: infd, offset: 2, SEEK_SET); |
163 | } |
164 | else |
165 | { |
166 | inoff = 3; |
167 | xlseek (fd: infd, offset: shift, SEEK_SET); |
168 | } |
169 | ftruncate (fd: outfd, length: 0); |
170 | xlseek (fd: outfd, offset: 0, SEEK_SET); |
171 | outoff = 0; |
172 | |
173 | /* First call copies current_size - 1 bytes. */ |
174 | TEST_COMPARE (copy_file_range (infd, pinoff, outfd, poutoff, |
175 | current_size, 0), current_size - 1); |
176 | char *buffer = xmalloc (n: current_size); |
177 | TEST_COMPARE (pread64 (outfd, buffer, current_size, 0), |
178 | current_size - 1); |
179 | TEST_VERIFY (memcmp (buffer, random_data + shift, current_size - 1) |
180 | == 0); |
181 | free (ptr: buffer); |
182 | |
183 | if (do_inoff) |
184 | { |
185 | TEST_COMPARE (inoff, current_size - 1 + shift); |
186 | TEST_COMPARE (xlseek (infd, 0, SEEK_CUR), 2); |
187 | } |
188 | else |
189 | TEST_COMPARE (xlseek (infd, 0, SEEK_CUR), current_size - 1 + shift); |
190 | if (do_outoff) |
191 | { |
192 | TEST_COMPARE (outoff, current_size - 1); |
193 | TEST_COMPARE (xlseek (outfd, 0, SEEK_CUR), 0); |
194 | } |
195 | else |
196 | TEST_COMPARE (xlseek (outfd, 0, SEEK_CUR), current_size - 1); |
197 | |
198 | /* First call copies zero bytes. */ |
199 | TEST_COMPARE (copy_file_range (infd, pinoff, outfd, poutoff, |
200 | current_size, 0), 0); |
201 | /* And the offsets are unchanged. */ |
202 | if (do_inoff) |
203 | { |
204 | TEST_COMPARE (inoff, current_size - 1 + shift); |
205 | TEST_COMPARE (xlseek (infd, 0, SEEK_CUR), 2); |
206 | } |
207 | else |
208 | TEST_COMPARE (xlseek (infd, 0, SEEK_CUR), current_size - 1 + shift); |
209 | if (do_outoff) |
210 | { |
211 | TEST_COMPARE (outoff, current_size - 1); |
212 | TEST_COMPARE (xlseek (outfd, 0, SEEK_CUR), 0); |
213 | } |
214 | else |
215 | TEST_COMPARE (xlseek (outfd, 0, SEEK_CUR), current_size - 1); |
216 | } |
217 | } |
218 | |
219 | /* A named test function. */ |
220 | struct test_case |
221 | { |
222 | const char *name; |
223 | void (*func) (void); |
224 | bool sizes; /* If true, call the test with different current_size values. */ |
225 | }; |
226 | |
227 | /* The available test cases. */ |
228 | static struct test_case tests[] = |
229 | { |
230 | { "simple_file_copy" , simple_file_copy, .sizes = true }, |
231 | { "short_copy" , short_copy, .sizes = true }, |
232 | }; |
233 | |
234 | static int |
235 | do_test (void) |
236 | { |
237 | for (unsigned char *p = random_data; p < array_end (random_data); ++p) |
238 | *p = rand () >> 24; |
239 | |
240 | infd = create_temp_file (base: "tst-copy_file_range-in-" , filename: &infile); |
241 | outfd = create_temp_file (base: "tst-copy_file_range-out-" , filename: &outfile); |
242 | { |
243 | ssize_t ret = copy_file_range (infd: infd, NULL, outfd: outfd, NULL, length: 0, flags: 0); |
244 | if (ret != 0) |
245 | { |
246 | if (errno == ENOSYS) |
247 | FAIL_UNSUPPORTED ("copy_file_range is not support on this system" ); |
248 | FAIL_EXIT1 ("copy_file_range probing call: %m" ); |
249 | } |
250 | } |
251 | xclose (infd); |
252 | xclose (outfd); |
253 | |
254 | for (do_inoff = 0; do_inoff < 2; ++do_inoff) |
255 | for (do_outoff = 0; do_outoff < 2; ++do_outoff) |
256 | for (struct test_case *test = tests; test < array_end (tests); ++test) |
257 | for (const int *size = typical_sizes; |
258 | size < array_end (typical_sizes); ++size) |
259 | { |
260 | current_size = *size; |
261 | if (test_verbose > 0) |
262 | printf (format: "info: %s do_inoff=%d do_outoff=%d current_size=%d\n" , |
263 | test->name, do_inoff, do_outoff, current_size); |
264 | |
265 | inoff = 0; |
266 | if (do_inoff) |
267 | pinoff = &inoff; |
268 | else |
269 | pinoff = NULL; |
270 | outoff = 0; |
271 | if (do_outoff) |
272 | poutoff = &outoff; |
273 | else |
274 | poutoff = NULL; |
275 | |
276 | infd = xopen (path: infile, O_RDWR | O_LARGEFILE, 0); |
277 | xftruncate (fd: infd, length: 0); |
278 | outfd = xopen (path: outfile, O_RDWR | O_LARGEFILE, 0); |
279 | xftruncate (fd: outfd, length: 0); |
280 | |
281 | test->func (); |
282 | |
283 | xclose (infd); |
284 | xclose (outfd); |
285 | |
286 | if (!test->sizes) |
287 | /* Skip the other sizes unless they have been |
288 | requested. */ |
289 | break; |
290 | } |
291 | |
292 | free (ptr: infile); |
293 | free (ptr: outfile); |
294 | |
295 | return 0; |
296 | } |
297 | |
298 | #include <support/test-driver.c> |
299 | |