1 /*
2  * Copyright (C) 2013-2021 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * This code is a complete clean re-write of the stress tool by
19  * Colin Ian King <colin.king@canonical.com> and attempts to be
20  * backwardly compatible with the stress tool by Amos Waterland
21  * <apw@rossby.metr.ou.edu> but has more stress tests and more
22  * functionality.
23  *
24  */
25 #include "stress-ng.h"
26 
27 static const stress_help_t help[] = {
28 	{ NULL,	"file-ioctl N",		"start N workers exercising file specific ioctls" },
29 	{ NULL,	"file-ioctl-ops N",	"stop after N file ioctl bogo operations" },
30 	{ NULL,	NULL,			NULL }
31 };
32 
33 #if (defined(FIONBIO) && defined(O_NONBLOCK)) || \
34     (defined(FIOASYNC) && defined(O_ASYNC))
check_flag(const stress_args_t * args,const char * ioctl_name,const int fd,const int flag,const int ret,const bool set)35 static void check_flag(
36 	const stress_args_t *args,
37 	const char *ioctl_name,
38 	const int fd,
39 	const int flag,
40 	const int ret,
41 	const bool set)
42 {
43 #if defined(F_GETFL)
44 	if (ret == 0) {
45 		int flags;
46 
47 		flags = fcntl(fd, F_GETFL, 0);
48 		/*
49 		 *  The fcntl failed, so checking is not a valid
50 		 *  thing to sanity check with.
51 		 */
52 		if (errno != 0)
53 			return;
54 		if ((set && !(flags & flag)) ||
55 		    (!set && (flags & flag)))
56 			pr_fail("%s: ioctl %s failed, unexpected flags when checked with F_GETFL\n",
57 				args->name, ioctl_name);
58 	}
59 #else
60 	(void)args;
61 	(void)ioctl_name;
62 	(void)fd;
63 	(void)flag;
64 	(void)ret;
65 #endif
66 }
67 #endif
68 
69 /*
70  *  stress_file_ioctl
71  *	stress file ioctls
72  */
stress_file_ioctl(const stress_args_t * args)73 static int stress_file_ioctl(const stress_args_t *args)
74 {
75 	char filename[PATH_MAX];
76 	int ret, fd;
77 	const int bad_fd = stress_get_bad_fd();
78 #if defined(FICLONE) || defined(FICLONERANGE)
79 	int dfd;
80 #endif
81 	const off_t file_sz = 8192;
82 	uint32_t rnd = stress_mwc32();
83 
84 	ret = stress_temp_dir_mk_args(args);
85 	if (ret < 0)
86 		return exit_status(-ret);
87 
88 	(void)stress_temp_filename_args(args, filename, sizeof(filename), rnd);
89 	fd = open(filename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
90 	if (fd < 0) {
91 		ret = exit_status(errno);
92 		pr_err("%s: cannot create %s\n", args->name, filename);
93 		(void)stress_temp_dir_rm_args(args);
94 		return ret;
95 	}
96 	(void)unlink(filename);
97 
98 #if defined(FICLONE) || defined(FICLONERANGE)
99 	(void)stress_temp_filename_args(args, filename, sizeof(filename), rnd + 1);
100 	dfd = open(filename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
101 	if (dfd < 0) {
102 		ret = exit_status(errno);
103 		(void)close(fd);
104 		(void)stress_temp_dir_rm_args(args);
105 		pr_err("%s: cannot create %s\n", args->name, filename);
106 		return ret;
107 	}
108 	(void)unlink(filename);
109 #endif
110 
111 	(void)shim_fallocate(fd, 0, 0, file_sz);
112 #if defined(FICLONE) || defined(FICLONERANGE)
113 	(void)shim_fallocate(dfd, 0, 0, file_sz);
114 #endif
115 	(void)shim_fsync(fd);
116 
117 	(void)bad_fd;
118 
119 	stress_set_proc_state(args->name, STRESS_STATE_RUN);
120 
121 	do {
122 		int exercised = 0;
123 
124 #if defined(FIOCLEX)
125 		{
126 			ret = ioctl(fd, FIOCLEX);
127 			(void)ret;
128 
129 			exercised++;
130 		}
131 #endif
132 #if defined(FIONCLEX)
133 		{
134 			ret = ioctl(fd, FIONCLEX);
135 			(void)ret;
136 
137 			exercised++;
138 		}
139 #endif
140 #if defined(FIONBIO)
141 		{
142 			int opt;
143 
144 			opt = 1;
145 			ret = ioctl(fd, FIONBIO, &opt);
146 #if defined(O_NONBLOCK)
147 			check_flag(args, "FIONBIO", fd, O_NONBLOCK, ret, true);
148 #else
149 			(void)ret;
150 #endif
151 
152 			opt = 0;
153 			ret = ioctl(fd, FIONBIO, &opt);
154 #if defined(O_NONBLOCK)
155 			check_flag(args, "FIONBIO", fd, O_NONBLOCK, ret, false);
156 #else
157 			(void)ret;
158 #endif
159 			exercised++;
160 		}
161 #endif
162 
163 #if defined(FIOASYNC)
164 		{
165 			int opt;
166 
167 			opt = 1;
168 			ret = ioctl(fd, FIOASYNC, &opt);
169 #if defined(O_ASYNC)
170 			check_flag(args, "FIONASYNC", fd, O_ASYNC, ret, true);
171 #else
172 			(void)ret;
173 #endif
174 
175 			opt = 0;
176 			ret = ioctl(fd, FIOASYNC, &opt);
177 #if defined(O_ASYNC)
178 			check_flag(args, "FIONASYNC", fd, O_ASYNC, ret, false);
179 #else
180 			(void)ret;
181 #endif
182 			exercised++;
183 		}
184 #endif
185 
186 #if defined(FIOQSIZE)
187 		{
188 			shim_loff_t sz;
189 			struct stat buf;
190 
191 			ret = fstat(fd, &buf);
192 			if (ret == 0) {
193 				ret = ioctl(fd, FIOQSIZE, &sz);
194 				if ((ret == 0) && (file_sz != buf.st_size))
195 					pr_fail("%s: ioctl FIOQSIZE failed, size %jd (filesize) vs %jd (reported)\n",
196 						args->name,
197 						(intmax_t)file_sz, (intmax_t)sz);
198 			}
199 			exercised++;
200 		}
201 #endif
202 
203 /* Disable this at the moment, it is fragile */
204 #if 0
205 #if defined(FIFREEZE) &&	\
206     defined(FITHAW)
207 		{
208 			ret = ioctl(fd, FIFREEZE);
209 			(void)ret;
210 			ret = ioctl(fd, FITHAW);
211 			(void)ret;
212 
213 			exercised++;
214 		}
215 #endif
216 #endif
217 
218 #if defined(FIGETBSZ)
219 		{
220 			int isz;
221 
222 			ret = ioctl(fd, FIGETBSZ, &isz);
223 			if ((ret == 0) && (isz < 1))
224 				pr_fail("%s: ioctl FIGETBSZ returned unusual block size %d\n",
225 					args->name, isz);
226 
227 			exercised++;
228 		}
229 #endif
230 
231 #if defined(FICLONE)
232 		{
233 			ret = ioctl(dfd, FICLONE, fd);
234 			(void)ret;
235 
236 			exercised++;
237 		}
238 #endif
239 
240 #if defined(FICLONERANGE)
241 		{
242 			struct file_clone_range fcr;
243 
244 			(void)memset(&fcr, 0, sizeof(fcr));
245 			fcr.src_fd = fd;
246 			fcr.src_offset = 0;
247 			fcr.src_length = file_sz;
248 			fcr.dest_offset = 0;
249 
250 			ret = ioctl(dfd, FICLONERANGE, &fcr);
251 			(void)ret;
252 
253 			exercised++;
254 		}
255 #endif
256 
257 #if defined(FIDEDUPERANGE)
258 		{
259 			const size_t sz = sizeof(struct file_dedupe_range) +
260 					  sizeof(struct file_dedupe_range_info);
261 			char buf[sz] ALIGNED(64);
262 
263 			struct file_dedupe_range *d = (struct file_dedupe_range *)buf;
264 
265 			d->src_offset = 0;
266 			d->src_length = file_sz;
267 			d->dest_count = 1;
268 			d->reserved1 = 0;
269 			d->reserved2 = 0;
270 			d->info[0].dest_fd = dfd;
271 			d->info[0].dest_offset = 0;
272 			/* Zero the return values */
273 			d->info[0].bytes_deduped = 0;
274 			d->info[0].status = 0;
275 			d->info[0].reserved = 0;
276 			ret = ioctl(fd, FIDEDUPERANGE, d);
277 			(void)ret;
278 
279 			/*
280 			 * and exercise illegal dest_count to force an
281 			 * ENOMEM error
282 			 */
283 			d->dest_count = (uint16_t)~0U;
284 			ret = ioctl(fd, FIDEDUPERANGE, d);
285 			(void)ret;
286 
287 			exercised++;
288 		}
289 #endif
290 
291 #if defined(FIONREAD)
292 		{
293 			int isz = 0;
294 
295 			ret = ioctl(fd, FIONREAD, &isz);
296 			(void)ret;
297 
298 			exercised++;
299 
300 			/*
301 			 *  exercise invalid fd
302 			 */
303 			ret = ioctl(bad_fd, FIONREAD, &isz);
304 			(void)ret;
305 
306 			exercised++;
307 		}
308 #endif
309 
310 
311 #if defined(FIONWRITE)
312 		{
313 			int isz = 0;
314 
315 			ret = ioctl(fd, FIONWRITE, &isz);
316 			(void)ret;
317 
318 			exercised++;
319 		}
320 #endif
321 
322 #if defined(FS_IOC_GETVERSION)
323 		{
324 			int ver;
325 
326 			ret = ioctl(fd, FS_IOC_GETVERSION, &ver);
327 			(void)ret;
328 
329 			exercised++;
330 		}
331 #endif
332 
333 #if defined(_IOW) &&	\
334     defined(__linux__)
335 
336 /*
337  *  These will eventually be in linux/falloc.h for libc, but
338  *  define a shim version for now.
339  */
340 struct shim_space_resv {
341 	int16_t		l_type;
342 	int16_t		l_whence;
343 	int64_t		l_start;
344 	int64_t		l_len;
345 	int32_t		l_sysid;
346 	uint32_t	l_pid;
347 	int32_t		l_pad[4];
348 };
349 
350 #if !defined(FS_IOC_RESVSP) &&		\
351     defined(_IOW)
352 #define FS_IOC_RESVSP		_IOW('X', 40, struct shim_space_resv)
353 #endif
354 #if !defined(FS_IOC_UNRESVSP) &&	\
355     defined(_IOW)
356 #define FS_IOC_UNRESVSP		_IOW('X', 41, struct shim_space_resv)
357 #endif
358 #if !defined(FS_IOC_RESVSP64) &&	\
359     defined(_IOW)
360 #define FS_IOC_RESVSP64		_IOW('X', 42, struct shim_space_resv)
361 #endif
362 #if !defined(FS_IOC_UNRESVSP64) &&	\
363     defined(_IOW)
364 #define FS_IOC_UNRESVSP64	_IOW('X', 43, struct shim_space_resv)
365 #endif
366 #if !defined(FS_IOC_ZERO_RANGE) &&	\
367     defined(_IOW)
368 #define FS_IOC_ZERO_RANGE	_IOW('X', 57, struct shim_space_resv)
369 #endif
370 
371 #if defined(FS_IOC_RESVSP)
372 		{
373 			struct shim_space_resv r;
374 
375 			(void)memset(&r, 0, sizeof(r));
376 			r.l_whence = SEEK_SET;
377 			r.l_start = (int64_t)0;
378 			r.l_len = (int64_t)file_sz * 2;
379 			ret = ioctl(fd, FS_IOC_RESVSP, &r);
380 			(void)ret;
381 
382 			if (lseek(fd, (off_t)0, SEEK_SET) != (off_t)-1) {
383 				(void)memset(&r, 0, sizeof(r));
384 				r.l_whence = SEEK_CUR;
385 				r.l_start = (int64_t)0;
386 				r.l_len = (int64_t)file_sz;
387 				ret = ioctl(fd, FS_IOC_RESVSP, &r);
388 				(void)ret;
389 
390 				(void)memset(&r, 0, sizeof(r));
391 				r.l_whence = SEEK_END;
392 				r.l_start = (int64_t)0;
393 				r.l_len = (int64_t)1;
394 				ret = ioctl(fd, FS_IOC_RESVSP, &r);
395 				(void)ret;
396 			}
397 
398 			exercised++;
399 		}
400 #endif
401 
402 #if defined(FS_IOC_RESVSP64)
403 		{
404 			struct shim_space_resv r;
405 
406 			(void)memset(&r, 0, sizeof(r));
407 			r.l_whence = SEEK_SET;
408 			r.l_start = (int64_t)0;
409 			r.l_len = (int64_t)file_sz * 2;
410 
411 			ret = ioctl(fd, FS_IOC_RESVSP64, &r);
412 			(void)ret;
413 
414 			if (lseek(fd, (off_t)0, SEEK_SET) != (off_t)-1) {
415 				(void)memset(&r, 0, sizeof(r));
416 				r.l_whence = SEEK_CUR;
417 				r.l_start = (int64_t)0;
418 				r.l_len = (int64_t)file_sz;
419 				ret = ioctl(fd, FS_IOC_RESVSP64, &r);
420 				(void)ret;
421 
422 				(void)memset(&r, 0, sizeof(r));
423 				r.l_whence = SEEK_END;
424 				r.l_start = (int64_t)0;
425 				r.l_len = (int64_t)1;
426 				ret = ioctl(fd, FS_IOC_RESVSP64, &r);
427 				(void)ret;
428 			}
429 
430 			exercised++;
431 		}
432 #endif
433 
434 #if defined(FS_IOC_UNRESVSP)
435 		{
436 			struct shim_space_resv r;
437 
438 			(void)memset(&r, 0, sizeof(r));
439 			r.l_whence = SEEK_SET;
440 			r.l_start = (int64_t)file_sz;
441 			r.l_len = (int64_t)file_sz * 2;
442 
443 			ret = ioctl(fd, FS_IOC_UNRESVSP, &r);
444 			(void)ret;
445 
446 			exercised++;
447 		}
448 #endif
449 
450 #if defined(FS_IOC_UNRESVSP64)
451 		{
452 			struct shim_space_resv r;
453 
454 			(void)memset(&r, 0, sizeof(r));
455 			r.l_whence = SEEK_SET;
456 			r.l_start = (int64_t)file_sz;
457 			r.l_len = (int64_t)file_sz * 2;
458 
459 			ret = ioctl(fd, FS_IOC_UNRESVSP64, &r);
460 			(void)ret;
461 
462 			exercised++;
463 		}
464 #endif
465 
466 #if defined(FS_IOC_ZERO_RANGE)
467 		{
468 			struct shim_space_resv r;
469 
470 			(void)memset(&r, 0, sizeof(r));
471 			r.l_whence = SEEK_SET;
472 			r.l_start = (int64_t)0;
473 			r.l_len = (int64_t)file_sz / 2;
474 
475 			ret = ioctl(fd, FS_IOC_ZERO_RANGE, &r);
476 			(void)ret;
477 
478 			exercised++;
479 		}
480 #endif
481 
482 #endif
483 
484 #if defined(FIBMAP)
485 		{
486 			int block;
487 
488 			block = 0;
489 			ret = ioctl(fd, FIBMAP, &block);
490 			(void)ret;
491 
492 			/*
493 			 *  and exercise huge block request
494 			 *  that should return -ERANGE or -EINVAL;
495 			 */
496 			block = -1;
497 			ret = ioctl(fd, FIBMAP, &block);
498 			(void)ret;
499 
500 			exercised++;
501 		}
502 #endif
503 		if (!exercised) {	/* cppcheck-suppress knownConditionTrueFalse */
504 			pr_inf("%s: no available file ioctls to exercise\n",
505 				args->name);
506 			ret = EXIT_NOT_IMPLEMENTED;
507 			goto tidy;
508 		}
509 
510 		inc_counter(args);
511 	} while (keep_stressing(args));
512 
513 	ret = EXIT_SUCCESS;
514 
515 tidy:
516 	stress_set_proc_state(args->name, STRESS_STATE_DEINIT);
517 #if defined(FICLONE) || defined(FICLONERANGE)
518 	(void)close(dfd);
519 #endif
520 	(void)close(fd);
521 	(void)stress_temp_dir_rm_args(args);
522 
523 	return ret;
524 }
525 
526 stressor_info_t stress_file_ioctl_info = {
527 	.stressor = stress_file_ioctl,
528 	.class = CLASS_FILESYSTEM | CLASS_OS,
529 	.help = help
530 };
531