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