1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2019 SUSE LLC
4 * Author: Christian Amann <camann@suse.com>
5 */
6
7 /*
8 * Tests basic error handling of the
9 * copy_file_range syscall
10 *
11 * 1) Try to copy contents to file open as readonly
12 * -> EBADF
13 * 2) Try to copy contents to directory -> EISDIR
14 * 3) Try to copy contents to a file opened with the
15 * O_APPEND flag -> EBADF
16 * 4) Try to copy contents to closed file descriptor
17 * -> EBADF
18 * 5) Try to copy contents with invalid 'flags' value
19 * -> EINVAL
20 * 6) Try to copy contents to a file chattred with +i
21 * flag -> EPERM
22 * 7) Try to copy contents to a swapfile ->ETXTBSY
23 * 8) Try to copy contents to the same file with overlapping
24 * ->EINVAL
25 * 9) Try to copy contents to a blkdev ->EINVAL
26 * 10) Try to copy contents to a chardev ->EINVAL
27 * 11) Try to copy contents to a FIFO ->EINVAL
28 * 12) Try to copy contenst to a PIPE ->EINVAL
29 * 13) Try to copy contents to a file with length beyond
30 * 16EiB wraps around 0 -> EOVERFLOW
31 * 14) Try to copy contents to a file with target file range
32 * beyond maximum supported file size ->EFBIG
33 */
34
35 #define _GNU_SOURCE
36
37 #include "tst_test.h"
38 #include "copy_file_range.h"
39
40 static int fd_src;
41 static int fd_dest;
42 static int fd_rdonly;
43 static int fd_dir;
44 static int fd_closed;
45 static int fd_append;
46 static int fd_immutable;
47 static int fd_swapfile;
48 static int fd_dup;
49 static int fd_blkdev;
50 static int fd_chrdev;
51 static int fd_fifo;
52 static int fd_pipe[2];
53 static int fd_copy;
54 static int need_unlink;
55
56 static int chattr_i_nsup;
57 static int swap_nsup;
58 static int cross_sup;
59 static int loop_devn;
60
61 static struct tcase {
62 int *copy_to_fd;
63 int flags;
64 int exp_err;
65 loff_t len;
66 const char *tname;
67 int new_error;
68 } tcases[] = {
69 {&fd_rdonly, 0, EBADF, CONTSIZE, "readonly file", 0},
70 {&fd_dir, 0, EISDIR, CONTSIZE, "directory", 0},
71 {&fd_append, 0, EBADF, CONTSIZE, "append to file", 0},
72 {&fd_closed, 0, EBADF, CONTSIZE, "closed file descriptor", 0},
73 {&fd_dest, -1, EINVAL, CONTSIZE, "invalid flags", 0},
74 {&fd_immutable, 0, EPERM, CONTSIZE, "immutable file", 1},
75 {&fd_swapfile, 0, ETXTBSY, CONTSIZE, "swap file", 1},
76 {&fd_dup, 0, EINVAL, CONTSIZE/2, "overlaping range", 1},
77 {&fd_blkdev, 0, EINVAL, CONTSIZE, "block device", 0},
78 {&fd_chrdev, 0, EINVAL, CONTSIZE, "char device", 0},
79 {&fd_fifo, 0, EINVAL, CONTSIZE, "fifo", 0},
80 {&fd_pipe[0], 0, EINVAL, CONTSIZE, "pipe", 0},
81 #ifdef TST_ABI64
82 {&fd_copy, 0, EOVERFLOW, ULLONG_MAX, "max length", 1},
83 #else
84 {&fd_copy, 0, EFBIG, ULLONG_MAX, "max length", 1},
85 #endif
86 {&fd_copy, 0, EFBIG, MIN_OFF, "max file size", 1},
87 };
88
run_command(char * command,char * option,char * file)89 static int run_command(char *command, char *option, char *file)
90 {
91 const char *const cmd[] = {command, option, file, NULL};
92 int ret;
93
94 ret = tst_cmd(cmd, NULL, NULL, TST_CMD_PASS_RETVAL);
95 switch (ret) {
96 case 0:
97 return 0;
98 case 255:
99 tst_res(TCONF, "%s binary not installed or failed", command);
100 return 1;
101 default:
102 tst_res(TCONF, "%s exited with %i", command, ret);
103 return 2;
104 }
105 }
106
verify_copy_file_range(unsigned int n)107 static void verify_copy_file_range(unsigned int n)
108 {
109 struct tcase *tc = &tcases[n];
110 loff_t dst = 0;
111
112 tst_res(TINFO, "Test #%d: %s", n, tc->tname);
113
114 if (tc->new_error && !cross_sup) {
115 tst_res(TCONF,
116 "copy_file_range() doesn't support cross-device, skip it");
117 return;
118 }
119 if (tc->copy_to_fd == &fd_immutable && chattr_i_nsup) {
120 tst_res(TCONF, "filesystem doesn't support chattr +i, skip it");
121 return;
122 }
123 if (tc->copy_to_fd == &fd_swapfile && swap_nsup) {
124 tst_res(TCONF, "filesystem doesn't support swapfile, skip it");
125 return;
126 }
127 if (tc->copy_to_fd == &fd_blkdev && loop_devn == -1) {
128 tst_res(TCONF, "filesystem doesn't have free loopdev, skip it");
129 return;
130 }
131
132 if (tc->copy_to_fd == &fd_copy)
133 dst = tst_max_lfs_filesize();
134
135 TEST(sys_copy_file_range(fd_src, 0, *tc->copy_to_fd,
136 &dst, tc->len, tc->flags));
137
138 if (TST_RET == -1) {
139 if (tc->exp_err == TST_ERR) {
140 tst_res(TPASS | TTERRNO,
141 "copy_file_range failed as expected");
142 } else {
143 tst_res(TFAIL | TTERRNO,
144 "copy_file_range failed unexpectedly; expected %s, but got",
145 tst_strerrno(tc->exp_err));
146 return;
147 }
148 } else {
149 tst_res(TFAIL,
150 "copy_file_range returned wrong value: %ld", TST_RET);
151 }
152 }
153
cleanup(void)154 static void cleanup(void)
155 {
156 if (fd_append > 0)
157 SAFE_CLOSE(fd_append);
158 if (fd_dir > 0)
159 SAFE_CLOSE(fd_dir);
160 if (fd_rdonly > 0)
161 SAFE_CLOSE(fd_rdonly);
162 if (fd_dest > 0)
163 SAFE_CLOSE(fd_dest);
164 if (fd_src > 0)
165 SAFE_CLOSE(fd_src);
166 if (fd_immutable > 0) {
167 run_command("chattr", "-i", FILE_IMMUTABLE_PATH);
168 SAFE_CLOSE(fd_immutable);
169 }
170 if (fd_swapfile > 0) {
171 run_command("swapoff", FILE_SWAP_PATH, NULL);
172 SAFE_CLOSE(fd_swapfile);
173 }
174 if (fd_dup > 0)
175 SAFE_CLOSE(fd_dup);
176 if (fd_copy > 0)
177 SAFE_CLOSE(fd_copy);
178 if (need_unlink > 0)
179 SAFE_UNLINK(FILE_FIFO);
180
181 if (fd_pipe[0] > 0) {
182 SAFE_CLOSE(fd_pipe[0]);
183 SAFE_CLOSE(fd_pipe[1]);
184 }
185 }
186
setup(void)187 static void setup(void)
188 {
189 syscall_info();
190 char dev_path[1024];
191
192 cross_sup = verify_cross_fs_copy_support(FILE_SRC_PATH, FILE_MNTED_PATH);
193
194 if (access(FILE_DIR_PATH, F_OK) == -1)
195 SAFE_MKDIR(FILE_DIR_PATH, 0777);
196 /*
197 * call tst_find_free_loopdev(), avoid overwriting its
198 * content on used loopdev.
199 */
200 loop_devn = tst_find_free_loopdev(dev_path, sizeof(dev_path));
201
202 SAFE_MKNOD(FILE_FIFO, S_IFIFO | 0777, 0);
203 need_unlink = 1;
204
205 fd_src = SAFE_OPEN(FILE_SRC_PATH, O_RDWR | O_CREAT, 0664);
206 fd_dest = SAFE_OPEN(FILE_DEST_PATH, O_RDWR | O_CREAT, 0664);
207 fd_rdonly = SAFE_OPEN(FILE_RDONL_PATH, O_RDONLY | O_CREAT, 0664);
208 fd_dir = SAFE_OPEN(FILE_DIR_PATH, O_DIRECTORY);
209 fd_closed = -1;
210 fd_append = SAFE_OPEN(FILE_DEST_PATH,
211 O_RDWR | O_CREAT | O_APPEND, 0664);
212 fd_immutable = SAFE_OPEN(FILE_IMMUTABLE_PATH, O_RDWR | O_CREAT, 0664);
213 fd_swapfile = SAFE_OPEN(FILE_SWAP_PATH, O_RDWR | O_CREAT, 0600);
214
215 if (loop_devn != -1)
216 fd_blkdev = SAFE_OPEN(dev_path, O_RDWR, 0600);
217
218 fd_chrdev = SAFE_OPEN(FILE_CHRDEV, O_RDWR, 0600);
219 fd_fifo = SAFE_OPEN(FILE_FIFO, O_RDWR, 0600);
220
221 SAFE_PIPE(fd_pipe);
222
223 SAFE_WRITE(1, fd_src, CONTENT, CONTSIZE);
224 close(fd_src);
225 fd_src = SAFE_OPEN(FILE_SRC_PATH, O_RDONLY, 0664);
226 fd_dup = SAFE_OPEN(FILE_SRC_PATH, O_WRONLY|O_CREAT, 0666);
227
228 fd_copy = SAFE_OPEN(FILE_COPY_PATH, O_RDWR | O_CREAT | O_TRUNC, 0664);
229 chattr_i_nsup = run_command("chattr", "+i", FILE_IMMUTABLE_PATH);
230
231 if (!tst_fs_has_free(".", sysconf(_SC_PAGESIZE) * 10, TST_BYTES)) {
232 tst_res(TCONF, "Insufficient disk space to create swap file");
233 swap_nsup = 3;
234 return;
235 }
236
237 if (tst_fill_file(FILE_SWAP_PATH, 0, sysconf(_SC_PAGESIZE), 10) != 0) {
238 tst_res(TCONF, "Failed to create swapfile");
239 swap_nsup = 4;
240 return;
241 }
242
243 swap_nsup = run_command("mkswap", FILE_SWAP_PATH, NULL);
244 swap_nsup = run_command("swapon", FILE_SWAP_PATH, NULL);
245 }
246
247 static struct tst_test test = {
248 .test = verify_copy_file_range,
249 .tcnt = ARRAY_SIZE(tcases),
250 .setup = setup,
251 .cleanup = cleanup,
252 .mount_device = 1,
253 .mntpoint = MNTPOINT,
254 .needs_root = 1,
255 .test_variants = TEST_VARIANTS,
256 };
257