/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * This code is a complete clean re-write of the stress tool by * Colin Ian King and attempts to be * backwardly compatible with the stress tool by Amos Waterland * but has more stress tests and more * functionality. * */ #include "stress-ng.h" static const stress_help_t help[] = { { NULL, "procfs N", "start N workers reading portions of /proc" }, { NULL, "procfs-ops N", "stop procfs workers after N bogo read operations" }, { NULL, NULL, NULL } }; #if defined(HAVE_LIB_PTHREAD) && defined(__linux__) #define PROC_BUF_SZ (4096) #define MAX_PROCFS_THREADS (4) typedef struct stress_ctxt { const stress_args_t *args; const char *path; bool writeable; } stress_ctxt_t; #if !defined(NSIO) #define NSIO 0xb7 #endif #if !defined(NS_GET_USERNS) #define NS_GET_USERNS _IO(NSIO, 0x1) #endif #if !defined(NS_GET_PARENT) #define NS_GET_PARENT _IO(NSIO, 0x2) #endif #if !defined(NS_GET_NSTYPE) #define NS_GET_NSTYPE _IO(NSIO, 0x3) #endif #if !defined(NS_GET_OWNER_UID) #define NS_GET_OWNER_UID _IO(NSIO, 0x4) #endif static sigset_t set; static shim_pthread_spinlock_t lock; static char proc_path[PATH_MAX]; static uint32_t mixup; static uint32_t path_sum(const char *path) { const char *ptr = path; register uint32_t sum = mixup; while (*ptr) { sum <<= 1; sum += *(ptr++); } return sum; } static int mixup_sort(const struct dirent **d1, const struct dirent **d2) { uint32_t s1, s2; s1 = path_sum((*d1)->d_name); s2 = path_sum((*d2)->d_name); if (s1 == s2) return 0; return (s1 < s2) ? -1 : 1; } /* * stress_proc_rw() * read a proc file */ static inline void stress_proc_rw( const stress_ctxt_t *ctxt, int32_t loops) { int fd; ssize_t ret, i = 0; char buffer[PROC_BUF_SZ]; char path[PATH_MAX]; const double threshold = 0.2; const size_t page_size = ctxt->args->page_size; off_t pos; while (loops == -1 || loops > 0) { double t_start; bool timeout = false; uint8_t *ptr; struct stat statbuf; bool writeable = true; ret = shim_pthread_spin_lock(&lock); if (ret) return; (void)shim_strlcpy(path, proc_path, sizeof(path)); (void)shim_pthread_spin_unlock(&lock); if (!*path || !keep_stressing_flag()) break; if (!strncmp(path, "/proc/self", 10)) writeable = false; if (!strncmp(path, "/proc", 5) && isdigit(path[5])) writeable = false; t_start = stress_time_now(); if ((fd = open(path, O_RDONLY | O_NONBLOCK)) < 0) return; if (stress_time_now() - t_start > threshold) { timeout = true; (void)close(fd); goto next; } /* * fstat the file */ ret = fstat(fd, &statbuf); #if defined(__linux__) /* * Linux name space symlinks can be exercised * with some special name space ioctls: */ if (statbuf.st_mode & S_IFLNK) { if (!strncmp(path, "/proc/self", 10) && (strstr(path, "/ns/"))) { int ns_fd; uid_t uid; ns_fd = ioctl(fd, NS_GET_USERNS); if (ns_fd >= 0) (void)close(ns_fd); ns_fd = ioctl(fd, NS_GET_PARENT); if (ns_fd >= 0) (void)close(ns_fd); ret = ioctl(fd, NS_GET_NSTYPE); (void)ret; /* The following returns -EINVAL */ ret = ioctl(fd, NS_GET_OWNER_UID, &uid); (void)ret; } } #endif /* * Multiple randomly sized reads */ while (i < (4096 * PROC_BUF_SZ)) { ssize_t sz = 1 + (stress_mwc32() % sizeof(buffer)); if (!keep_stressing_flag()) break; ret = read(fd, buffer, sz); if (ret < 0) break; if (ret < sz) break; i += sz; if (stress_time_now() - t_start > threshold) { timeout = true; (void)close(fd); goto next; } } (void)close(fd); if ((fd = open(path, O_RDONLY | O_NONBLOCK)) < 0) return; if (stress_time_now() - t_start > threshold) { timeout = true; (void)close(fd); goto next; } /* * Zero sized reads */ ret = read(fd, buffer, 0); if (ret < 0) goto err; if (stress_time_now() - t_start > threshold) { timeout = true; (void)close(fd); goto next; } /* * Broken offset reads, see Linux commit * 3bfa7e141b0bbb818b25e0daafb65aee92e49ac4 * "fs/seq_file.c: seq_read(): add info message * about buggy .next functions" */ pos = lseek(fd, 0, SEEK_SET); if (pos < 0) goto mmap_test; ret = read(fd, buffer, sizeof(buffer)); if (ret < 0) goto mmap_test; if (ret < (ssize_t)(sizeof(buffer) >> 1)) { char *bptr; for (bptr = buffer; *bptr && *bptr != '\n'; bptr++) ; if (*bptr == '\n') { const off_t offset = 2 + (bptr - buffer); pos = lseek(fd, offset, SEEK_SET); if (pos == offset) { /* Causes incorrect 2nd read */ ret = read(fd, buffer, sizeof(buffer)); (void)ret; } } } mmap_test: /* * mmap it */ ptr = mmap(NULL, page_size, PROT_READ, MAP_SHARED | MAP_ANONYMOUS, fd, 0); if (ptr != MAP_FAILED) { stress_uint8_put(*ptr); (void)munmap(ptr, page_size); } if (stress_time_now() - t_start > threshold) { timeout = true; (void)close(fd); goto next; } #if defined(FIONREAD) { int nbytes; /* * ioctl(), bytes ready to read */ ret = ioctl(fd, FIONREAD, &nbytes); (void)ret; } if (stress_time_now() - t_start > threshold) { timeout = true; (void)close(fd); goto next; } #endif #if defined(HAVE_POLL_H) { struct pollfd fds[1]; fds[0].fd = fd; fds[0].events = POLLIN; fds[0].revents = 0; ret = poll(fds, 1, 0); (void)ret; } #endif /* * Seek and read */ pos = lseek(fd, 0, SEEK_SET); if (pos == (off_t)-1) goto err; pos = lseek(fd, 1, SEEK_CUR); if (pos == (off_t)-1) goto err; pos = lseek(fd, 0, SEEK_END); if (pos == (off_t)-1) goto err; pos = lseek(fd, 1, SEEK_SET); if (pos == (off_t)-1) goto err; if (stress_time_now() - t_start > threshold) { timeout = true; (void)close(fd); goto next; } ret = read(fd, buffer, 1); (void)ret; err: (void)close(fd); if (stress_time_now() - t_start > threshold) { timeout = true; goto next; } if (writeable && ctxt->writeable) { /* * Zero sized writes */ if ((fd = open(path, O_WRONLY | O_NONBLOCK)) < 0) return; ret = write(fd, buffer, 0); (void)ret; (void)close(fd); } next: if (loops > 0) { if (timeout) break; loops--; } } } /* * stress_proc_rw_thread * keep exercising a procfs entry until * controlling thread triggers an exit */ static void *stress_proc_rw_thread(void *ctxt_ptr) { static void *nowt = NULL; stress_ctxt_t *ctxt = (stress_ctxt_t *)ctxt_ptr; /* * Block all signals, let controlling thread * handle these */ (void)sigprocmask(SIG_BLOCK, &set, NULL); while (keep_stressing_flag()) stress_proc_rw(ctxt, -1); return &nowt; } /* * stress_proc_dir() * read directory */ static void stress_proc_dir( const stress_ctxt_t *ctxt, const char *path, const bool recurse, const int depth) { struct dirent **dlist; const stress_args_t *args = ctxt->args; int32_t loops = args->instance < 8 ? args->instance + 1 : 8; int i, n, ret; char tmp[PATH_MAX]; if (!keep_stressing_flag()) return; /* Don't want to go too deep */ if (depth > 20) return; mixup = stress_mwc32(); dlist = NULL; n = scandir(path, &dlist, NULL, mixup_sort); if (n <= 0) { stress_dirent_list_free(dlist, n); return; } /* Non-directories files first */ for (i = 0; (i < n) && keep_stressing_flag(); i++) { struct dirent *d = dlist[i]; if (stress_is_dot_filename(d->d_name)) { free(d); dlist[i] = NULL; continue; } if ((d->d_type == DT_REG) || (d->d_type == DT_LNK)) { ret = shim_pthread_spin_lock(&lock); if (!ret) { (void)snprintf(tmp, sizeof(tmp), "%s/%s", path, d->d_name); (void)shim_strlcpy(proc_path, tmp, sizeof(proc_path)); (void)shim_pthread_spin_unlock(&lock); stress_proc_rw(ctxt, loops); inc_counter(args); } free(d); dlist[i] = NULL; } } if (!recurse) { stress_dirent_list_free(dlist, n); return; } /* Now recurse on directories */ for (i = 0; i < n && keep_stressing_flag(); i++) { struct dirent *d = dlist[i]; if (d && d->d_type == DT_DIR) { (void)snprintf(tmp, sizeof(tmp), "%s/%s", path, d->d_name); free(d); dlist[i] = NULL; stress_proc_dir(ctxt, tmp, recurse, depth + 1); inc_counter(args); } } stress_dirent_list_free(dlist, n); } /* * stress_random_pid() * return /proc/$pid where pid is a random existing process ID */ static char *stress_random_pid(void) { struct dirent **dlist = NULL; static char path[PATH_MAX]; int i, n; unsigned int j; (void)shim_strlcpy(path, "/proc/self", sizeof(path)); n = scandir("/proc", &dlist, NULL, mixup_sort); if (!n) { stress_dirent_list_free(dlist, n); return path; } /* * try 32 random probes before giving up */ for (i = 0, j = 0; i < 32; i++) { char *name; j += stress_mwc32(); j %= n; name = dlist[j]->d_name; if (isdigit(name[0])) { (void)snprintf(path, sizeof(path), "/proc/%s", name); break; } } stress_dirent_list_free(dlist, n); return path; } /* * stress_procfs * stress reading all of /proc */ static int stress_procfs(const stress_args_t *args) { size_t i; pthread_t pthreads[MAX_PROCFS_THREADS]; int rc, ret[MAX_PROCFS_THREADS]; stress_ctxt_t ctxt; (void)sigfillset(&set); shim_strlcpy(proc_path, "/proc/self", sizeof(proc_path)); ctxt.args = args; ctxt.writeable = (geteuid() != 0); rc = shim_pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE); if (rc) { pr_inf("%s: pthread_spin_init failed, errno=%d (%s)\n", args->name, rc, strerror(rc)); return EXIT_NO_RESOURCE; } (void)memset(ret, 0, sizeof(ret)); for (i = 0; i < MAX_PROCFS_THREADS; i++) { ret[i] = pthread_create(&pthreads[i], NULL, stress_proc_rw_thread, &ctxt); } i = args->instance; do { i %= 15; switch (i) { case 0: stress_proc_dir(&ctxt, "/proc", false, 0); break; case 1: stress_proc_dir(&ctxt, "/proc/self", true, 0); break; case 2: stress_proc_dir(&ctxt, "/proc/thread_self", true, 0); break; case 3: stress_proc_dir(&ctxt, "/proc/sys", true, 0); break; case 4: stress_proc_dir(&ctxt, "/proc/sysvipc", true, 0); break; case 5: stress_proc_dir(&ctxt, "/proc/fs", true, 0); break; case 6: stress_proc_dir(&ctxt, "/proc/bus", true, 0); break; case 7: stress_proc_dir(&ctxt, "/proc/irq", true, 0); break; case 8: stress_proc_dir(&ctxt, "/proc/scsi", true, 0); break; case 9: stress_proc_dir(&ctxt, "/proc/tty", true, 0); break; case 10: stress_proc_dir(&ctxt, "/proc/driver", true, 0); break; case 11: stress_proc_dir(&ctxt, "/proc/tty", true, 0); break; case 12: stress_proc_dir(&ctxt, "/proc/self", true, 0); break; case 13: stress_proc_dir(&ctxt, "/proc/1", true, 0); break; case 14: stress_proc_dir(&ctxt, stress_random_pid(), true, 0); break; default: break; } i++; inc_counter(args); if (!keep_stressing()) break; } while (keep_stressing()); rc = shim_pthread_spin_lock(&lock); if (rc) { pr_dbg("%s: failed to lock spin lock for sysfs_path\n", args->name); } else { shim_strlcpy(proc_path, "", sizeof(proc_path)); rc = shim_pthread_spin_unlock(&lock); (void)rc; } for (i = 0; i < MAX_PROCFS_THREADS; i++) { if (ret[i] == 0) (void)pthread_join(pthreads[i], NULL); } (void)shim_pthread_spin_destroy(&lock); return EXIT_SUCCESS; } stressor_info_t stress_procfs_info = { .stressor = stress_procfs, .class = CLASS_FILESYSTEM | CLASS_OS, .help = help }; #else stressor_info_t stress_procfs_info = { .stressor = stress_not_implemented, .class = CLASS_FILESYSTEM | CLASS_OS, .help = help }; #endif