1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) Linux Test Project, 2019
4 */
5
6 /*
7 * This tests the fundamental functionalities of the copy_file_range
8 * syscall. It does so by copying the contents of one file into
9 * another using various different combinations for length and
10 * input/output offsets.
11 *
12 * After a copy is done this test checks if the contents of both files
13 * are equal at the given offsets. It is also inspected if the offsets
14 * of the file descriptors are advanced correctly.
15 */
16
17 #define _GNU_SOURCE
18
19 #include "tst_test.h"
20 #include "tst_safe_stdio.h"
21 #include "copy_file_range.h"
22
23 static int page_size;
24 static int errcount, numcopies;
25 static int fd_in, fd_out, cross_sup;
26
27 static struct tcase {
28 char *path;
29 int flags;
30 char *message;
31 } tcases[] = {
32 {FILE_DEST_PATH, 0, "non cross-device"},
33 {FILE_MNTED_PATH, 1, "cross-device"},
34 };
35
check_file_content(const char * fname1,const char * fname2,loff_t * off1,loff_t * off2,size_t len)36 static int check_file_content(const char *fname1, const char *fname2,
37 loff_t *off1, loff_t *off2, size_t len)
38 {
39 FILE *fp1, *fp2;
40 int ch1, ch2;
41 size_t count = 0;
42
43 fp1 = SAFE_FOPEN(fname1, "r");
44 if (off1 && fseek(fp1, *off1, SEEK_SET))
45 tst_brk(TBROK | TERRNO, "fseek() failed");
46
47 fp2 = SAFE_FOPEN(fname2, "r");
48 if (off2 && fseek(fp2, *off2, SEEK_SET))
49 tst_brk(TBROK | TERRNO, "fseek() failed");
50
51 do {
52 ch1 = getc(fp1);
53 ch2 = getc(fp2);
54 count++;
55 } while ((count < len) && (ch1 == ch2));
56
57 SAFE_FCLOSE(fp1);
58 SAFE_FCLOSE(fp2);
59
60 return !(ch1 == ch2);
61 }
62
check_file_offset(const char * m,int fd,loff_t len,loff_t * off_before,loff_t * off_after)63 static int check_file_offset(const char *m, int fd, loff_t len,
64 loff_t *off_before, loff_t *off_after)
65 {
66 loff_t fd_off = SAFE_LSEEK(fd, 0, SEEK_CUR);
67 int ret = 0;
68
69 if (off_before) {
70 /*
71 * copy_file_range offset is given:
72 * - fd offset should stay 0,
73 * - copy_file_range offset is updated
74 */
75 if (fd_off != 0) {
76 tst_res(TFAIL,
77 "%s fd offset unexpectedly changed: %ld",
78 m, (long)fd_off);
79 ret = 1;
80
81 } else if (*off_before + len != *off_after) {
82 tst_res(TFAIL, "%s offset unexpected value: %ld",
83 m, (long)*off_after);
84 ret = 1;
85 }
86 }
87 /*
88 * no copy_file_range offset given:
89 * - fd offset advanced by length
90 */
91 else if (fd_off != len) {
92 tst_res(TFAIL, "%s fd offset unexpected value: %ld",
93 m, (long)fd_off);
94 ret = 1;
95 }
96
97 return ret;
98 }
99
test_one(size_t len,loff_t * off_in,loff_t * off_out,char * path)100 static void test_one(size_t len, loff_t *off_in, loff_t *off_out, char *path)
101 {
102 int ret;
103 size_t to_copy = len;
104 loff_t off_in_value_copy, off_out_value_copy;
105 loff_t *off_new_in = &off_in_value_copy;
106 loff_t *off_new_out = &off_out_value_copy;
107 char str_off_in[32], str_off_out[32];
108
109 if (off_in) {
110 off_in_value_copy = *off_in;
111 sprintf(str_off_in, "%ld", (long)*off_in);
112 } else {
113 off_new_in = NULL;
114 strcpy(str_off_in, "NULL");
115 }
116
117 if (off_out) {
118 off_out_value_copy = *off_out;
119 sprintf(str_off_out, "%ld", (long)*off_out);
120 } else {
121 off_new_out = NULL;
122 strcpy(str_off_out, "NULL");
123 }
124
125 /*
126 * copy_file_range() will return the number of bytes copied between
127 * files. This could be less than the length originally requested.
128 */
129 do {
130 TEST(sys_copy_file_range(fd_in, off_new_in, fd_out,
131 off_new_out, to_copy, 0));
132 if (TST_RET == -1) {
133 tst_res(TFAIL | TTERRNO, "copy_file_range() failed");
134 errcount++;
135 return;
136 }
137
138 to_copy -= TST_RET;
139 } while (to_copy > 0);
140
141 ret = check_file_content(FILE_SRC_PATH, path,
142 off_in, off_out, len);
143 if (ret) {
144 tst_res(TFAIL, "file contents do not match");
145 errcount++;
146 return;
147 }
148
149 ret |= check_file_offset("(in)", fd_in, len, off_in, off_new_in);
150 ret |= check_file_offset("(out)", fd_out, len, off_out, off_new_out);
151
152 if (ret != 0) {
153 tst_res(TFAIL, "off_in: %s, off_out: %s, len: %ld",
154 str_off_in, str_off_out, (long)len);
155 errcount++;
156 }
157 }
158
open_files(char * path)159 static void open_files(char *path)
160 {
161 fd_in = SAFE_OPEN(FILE_SRC_PATH, O_RDONLY);
162 fd_out = SAFE_OPEN(path, O_CREAT | O_WRONLY | O_TRUNC, 0644);
163 }
164
close_files(void)165 static void close_files(void)
166 {
167 if (fd_out > 0)
168 SAFE_CLOSE(fd_out);
169 if (fd_in > 0)
170 SAFE_CLOSE(fd_in);
171 }
172
copy_file_range_verify(unsigned int n)173 static void copy_file_range_verify(unsigned int n)
174 {
175 int i, j, k;
176 struct tcase *tc = &tcases[n];
177
178 if (tc->flags && !cross_sup) {
179 tst_res(TCONF,
180 "copy_file_range() doesn't support cross-device, skip it");
181 return;
182 }
183
184 errcount = numcopies = 0;
185 size_t len_arr[] = {11, page_size-1, page_size, page_size+1};
186 loff_t off_arr_values[] = {0, 17, page_size-1, page_size, page_size+1};
187
188 int num_offsets = ARRAY_SIZE(off_arr_values) + 1;
189 loff_t *off_arr[num_offsets];
190
191 off_arr[0] = NULL;
192 for (i = 1; i < num_offsets; i++)
193 off_arr[i] = &off_arr_values[i-1];
194
195 /* Test all possible cobinations of given lengths and offsets */
196 for (i = 0; i < (int)ARRAY_SIZE(len_arr); i++)
197 for (j = 0; j < num_offsets; j++)
198 for (k = 0; k < num_offsets; k++) {
199 open_files(tc->path);
200 test_one(len_arr[i], off_arr[j], off_arr[k], tc->path);
201 close_files();
202 numcopies++;
203 }
204
205 if (errcount == 0)
206 tst_res(TPASS,
207 "%s copy_file_range completed all %d copy jobs successfully!",
208 tc->message, numcopies);
209 else
210 tst_res(TFAIL, "%s copy_file_range failed %d of %d copy jobs.",
211 tc->message, errcount, numcopies);
212 }
213
setup(void)214 static void setup(void)
215 {
216 syscall_info();
217 page_size = getpagesize();
218 cross_sup = verify_cross_fs_copy_support(FILE_SRC_PATH, FILE_MNTED_PATH);
219 }
220
cleanup(void)221 static void cleanup(void)
222 {
223 close_files();
224 }
225
226 static struct tst_test test = {
227 .setup = setup,
228 .cleanup = cleanup,
229 .tcnt = ARRAY_SIZE(tcases),
230 .mount_device = 1,
231 .mntpoint = MNTPOINT,
232 .all_filesystems = 1,
233 .test = copy_file_range_verify,
234 .test_variants = TEST_VARIANTS,
235 };
236