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 #define MAX_FSTAT_THREADS	(4)
28 #define FSTAT_LOOPS		(16)
29 
30 static volatile bool keep_running;
31 static sigset_t set;
32 
33 static const stress_help_t help[] = {
34 	{ NULL,	"fstat N",	  "start N workers exercising fstat on files" },
35 	{ NULL,	"fstat-ops N",	  "stop after N fstat bogo operations" },
36 	{ NULL,	"fstat-dir path", "fstat files in the specified directory" },
37 	{ NULL,	NULL,		  NULL }
38 };
39 
40 /* paths we should never stat */
41 static const char *blocklist[] = {
42 	"/dev/watchdog"
43 };
44 
45 #define IGNORE_STAT	0x0001
46 #define IGNORE_LSTAT	0x0002
47 #define IGNORE_STATX	0x0004
48 #define IGNORE_FSTAT	0x0008
49 #define IGNORE_ALL	0x000f
50 
51 /* stat path information */
52 typedef struct stat_info {
53 	struct stat_info *next;		/* next stat_info in list */
54 	char		*path;		/* path to stat */
55 	uint16_t	ignore;		/* true to ignore this path */
56 	bool		access;		/* false if we can't access path */
57 } stress_stat_info_t;
58 
59 /* Thread context information */
60 typedef struct ctxt {
61 	const stress_args_t *args;	/* Stressor args */
62 	stress_stat_info_t *si;		/* path stat information */
63 	const uid_t euid;		/* euid of process */
64 	const int bad_fd;		/* bad/invalid fd */
65 } stress_ctxt_t;
66 
stress_set_fstat_dir(const char * opt)67 static int stress_set_fstat_dir(const char *opt)
68 {
69 	return stress_set_setting("fstat-dir", TYPE_ID_STR, opt);
70 }
71 
72 /*
73  *  handle_fstat_sigalrm()
74  *      catch SIGALRM
75  */
handle_fstat_sigalrm(int signum)76 static void MLOCKED_TEXT handle_fstat_sigalrm(int signum)
77 {
78 	(void)signum;
79 
80 	keep_running = false;
81 	keep_stressing_set_flag(false);
82 }
83 
84 /*
85  *  do_not_stat()
86  *	Check if file should not be stat'd
87  */
do_not_stat(const char * filename)88 static bool do_not_stat(const char *filename)
89 {
90 	size_t i;
91 
92 	for (i = 0; i < SIZEOF_ARRAY(blocklist); i++) {
93 		if (!strncmp(filename, blocklist[i], strlen(blocklist[i])))
94 			return true;
95 	}
96 	return false;
97 }
98 
stress_fstat_helper(const stress_ctxt_t * ctxt)99 static void stress_fstat_helper(const stress_ctxt_t *ctxt)
100 {
101 	struct stat buf;
102 #if defined(AT_EMPTY_PATH) &&	\
103     defined(AT_SYMLINK_NOFOLLOW)
104 	struct shim_statx bufx;
105 #endif
106 	stress_stat_info_t *si = ctxt->si;
107 	int ret;
108 
109 	if ((stat(si->path, &buf) < 0) && (errno != ENOMEM)) {
110 		si->ignore |= IGNORE_STAT;
111 	}
112 	if ((lstat(si->path, &buf) < 0) && (errno != ENOMEM)) {
113 		si->ignore |= IGNORE_LSTAT;
114 	}
115 #if defined(AT_EMPTY_PATH) &&	\
116     defined(AT_SYMLINK_NOFOLLOW)
117 	/* Heavy weight statx */
118 	if ((shim_statx(AT_EMPTY_PATH, si->path, AT_SYMLINK_NOFOLLOW,
119 		SHIM_STATX_ALL, &bufx) < 0) && (errno != ENOMEM)) {
120 		si->ignore |= IGNORE_STATX;
121 	}
122 
123 	/* invalid dfd in statx */
124 	ret = shim_statx(-1, "baddfd", AT_SYMLINK_NOFOLLOW, SHIM_STATX_ALL, &bufx);
125 	(void)ret;
126 
127 	/* invalid path in statx */
128 	ret = shim_statx(AT_EMPTY_PATH, "", AT_SYMLINK_NOFOLLOW, SHIM_STATX_ALL, &bufx);
129 	(void)ret;
130 
131 	/* invalid mask in statx */
132 	ret = shim_statx(AT_EMPTY_PATH, si->path, ~0, SHIM_STATX_ALL, &bufx);
133 	(void)ret;
134 
135 	/* invalid flags in statx */
136 	ret = shim_statx(AT_EMPTY_PATH, si->path, AT_SYMLINK_NOFOLLOW, ~0, &bufx);
137 	(void)ret;
138 #endif
139 	/*
140 	 *  Opening /dev files such as /dev/urandom
141 	 *  may block when running as root, so
142 	 *  avoid this.
143 	 */
144 	if ((si->access) && (ctxt->euid)) {
145 		int fd;
146 
147 		fd = open(si->path, O_RDONLY | O_NONBLOCK);
148 		if (fd < 0) {
149 			si->access = false;
150 			return;
151 		}
152 		if ((fstat(fd, &buf) < 0) && (errno != ENOMEM))
153 			si->ignore |= IGNORE_FSTAT;
154 		(void)close(fd);
155 	}
156 
157 	/* Exercise stat on an invalid path, ENOENT */
158 	ret = stat("", &buf);
159 	(void)ret;
160 
161 	/* Exercise lstat on an invalid path, ENOENT */
162 	ret = lstat("", &buf);
163 	(void)ret;
164 
165 	/* Exercise fstat on an invalid fd, EBADF */
166 	ret = fstat(ctxt->bad_fd, &buf);
167 	(void)ret;
168 }
169 
170 #if defined(HAVE_LIB_PTHREAD)
171 /*
172  *  stress_fstat_thread
173  *	keep exercising a file until
174  *	controlling thread triggers an exit
175  */
stress_fstat_thread(void * ctxt_ptr)176 static void *stress_fstat_thread(void *ctxt_ptr)
177 {
178 	static void *nowt = NULL;
179 	const stress_ctxt_t *ctxt = (const stress_ctxt_t *)ctxt_ptr;
180 
181 	/*
182 	 *  Block all signals, let controlling thread
183 	 *  handle these
184 	 */
185 #if !defined(__APPLE__)
186 	(void)sigprocmask(SIG_BLOCK, &set, NULL);
187 #endif
188 
189 	while (keep_running && keep_stressing_flag()) {
190 		size_t i;
191 
192 		for (i = 0; i < FSTAT_LOOPS; i++) {
193 			if (!keep_stressing_flag())
194 				break;
195 			stress_fstat_helper(ctxt);
196 		}
197 		(void)shim_sched_yield();
198 	}
199 
200 	return &nowt;
201 }
202 #endif
203 
204 /*
205  *  stress_fstat_threads()
206  *	create a bunch of threads to thrash a file
207  */
stress_fstat_threads(const stress_args_t * args,stress_stat_info_t * si,const uid_t euid)208 static void stress_fstat_threads(const stress_args_t *args, stress_stat_info_t *si, const uid_t euid)
209 {
210 	size_t i;
211 #if defined(HAVE_LIB_PTHREAD)
212 	pthread_t pthreads[MAX_FSTAT_THREADS];
213 	int ret[MAX_FSTAT_THREADS];
214 #endif
215 	stress_ctxt_t ctxt = {
216 		.args 	= args,
217 		.si 	= si,
218 		.euid	= euid,
219 		.bad_fd = stress_get_bad_fd()
220 	};
221 
222 	keep_running = true;
223 #if defined(HAVE_LIB_PTHREAD)
224 	(void)memset(ret, 0, sizeof(ret));
225 	(void)memset(pthreads, 0, sizeof(pthreads));
226 
227 	for (i = 0; i < MAX_FSTAT_THREADS; i++) {
228 		ret[i] = pthread_create(&pthreads[i], NULL,
229 				stress_fstat_thread, &ctxt);
230 	}
231 #endif
232 	for (i = 0; i < FSTAT_LOOPS; i++) {
233 		if (!keep_stressing_flag())
234 			break;
235 		stress_fstat_helper(&ctxt);
236 	}
237 	keep_running = false;
238 
239 #if defined(HAVE_LIB_PTHREAD)
240 	for (i = 0; i < MAX_FSTAT_THREADS; i++) {
241 		if (ret[i] == 0)
242 			(void)pthread_join(pthreads[i], NULL);
243 	}
244 #endif
245 }
246 
247 /*
248  *  stress_fstat()
249  *	stress system with fstat
250  */
stress_fstat(const stress_args_t * args)251 static int stress_fstat(const stress_args_t *args)
252 {
253 	stress_stat_info_t *si;
254 	static stress_stat_info_t *stat_info;
255 	struct dirent *d;
256 	NOCLOBBER int ret = EXIT_FAILURE;
257 	bool stat_some;
258 	const uid_t euid = geteuid();
259 	DIR *dp;
260 	char *fstat_dir = "/dev";
261 
262 	(void)stress_get_setting("fstat-dir", &fstat_dir);
263 
264 	if (stress_sighandler(args->name, SIGALRM, handle_fstat_sigalrm, NULL) < 0)
265 		return EXIT_FAILURE;
266 
267 	if ((dp = opendir(fstat_dir)) == NULL) {
268 		pr_err("%s: opendir on %s failed: errno=%d: (%s)\n",
269 			args->name, fstat_dir, errno, strerror(errno));
270 		return EXIT_FAILURE;
271 	}
272 
273 	/* Cache all the directory entries */
274 	while ((d = readdir(dp)) != NULL) {
275 		char path[PATH_MAX];
276 
277 		if (!keep_stressing_flag()) {
278 			ret = EXIT_SUCCESS;
279 			(void)closedir(dp);
280 			goto free_cache;
281 		}
282 
283 		(void)stress_mk_filename(path, sizeof(path), fstat_dir, d->d_name);
284 		if (do_not_stat(path))
285 			continue;
286 		if ((si = calloc(1, sizeof(*si))) == NULL) {
287 			pr_err("%s: out of memory\n", args->name);
288 			(void)closedir(dp);
289 			goto free_cache;
290 		}
291 		if ((si->path = strdup(path)) == NULL) {
292 			pr_err("%s: out of memory\n", args->name);
293 			free(si);
294 			(void)closedir(dp);
295 			goto free_cache;
296 		}
297 		si->ignore = 0;
298 		si->access = true;
299 		si->next = stat_info;
300 		stat_info = si;
301 	}
302 	(void)closedir(dp);
303 
304 	(void)sigfillset(&set);
305 	stress_set_proc_state(args->name, STRESS_STATE_RUN);
306 
307 	do {
308 		stat_some = false;
309 
310 		for (si = stat_info; keep_stressing_flag() && si; si = si->next) {
311 			if (!keep_stressing(args))
312 				break;
313 			if (si->ignore == IGNORE_ALL)
314 				continue;
315 			stress_fstat_threads(args, si, euid);
316 
317 			stat_some = true;
318 			inc_counter(args);
319 		}
320 	} while (stat_some && keep_stressing(args));
321 
322 	ret = EXIT_SUCCESS;
323 free_cache:
324 	stress_set_proc_state(args->name, STRESS_STATE_DEINIT);
325 
326 	/* Free cache */
327 	for (si = stat_info; si; ) {
328 		stress_stat_info_t *next = si->next;
329 
330 		free(si->path);
331 		free(si);
332 		si = next;
333 	}
334 
335 	return ret;
336 }
337 
338 static const stress_opt_set_func_t opt_set_funcs[] = {
339 	{ OPT_fstat_dir,	stress_set_fstat_dir },
340 	{ 0,			NULL }
341 };
342 
343 stressor_info_t stress_fstat_info = {
344 	.stressor = stress_fstat,
345 	.class = CLASS_FILESYSTEM | CLASS_OS,
346 	.opt_set_funcs = opt_set_funcs,
347 	.help = help
348 };
349