• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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