xref: /linux/tools/testing/selftests/filesystems/file_stressor.c (revision 364eeb79a213fcf9164208b53764223ad522d6b3)
1 // SPDX-License-Identifier: GPL-2.0
2 #define _GNU_SOURCE
3 #define __SANE_USERSPACE_TYPES__
4 
5 #include <fcntl.h>
6 #include <limits.h>
7 #include <pthread.h>
8 #include <sched.h>
9 #include <stdio.h>
10 #include <string.h>
11 #include <sys/stat.h>
12 #include <sys/mount.h>
13 #include <unistd.h>
14 
15 #include "../kselftest_harness.h"
16 
17 #include <linux/types.h>
18 #include <linux/mount.h>
19 #include <sys/syscall.h>
20 
21 static inline int sys_fsopen(const char *fsname, unsigned int flags)
22 {
23 	return syscall(__NR_fsopen, fsname, flags);
24 }
25 
26 static inline int sys_fsconfig(int fd, unsigned int cmd, const char *key,
27 			       const char *value, int aux)
28 {
29 	return syscall(__NR_fsconfig, fd, cmd, key, value, aux);
30 }
31 
32 static inline int sys_fsmount(int fd, unsigned int flags,
33 			      unsigned int attr_flags)
34 {
35 	return syscall(__NR_fsmount, fd, flags, attr_flags);
36 }
37 
38 #ifndef MOVE_MOUNT_F_EMPTY_PATH
39 #define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 /* Empty from path permitted */
40 #endif
41 
42 static inline int sys_move_mount(int from_dfd, const char *from_pathname,
43 				 int to_dfd, const char *to_pathname,
44 				 unsigned int flags)
45 {
46 	return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd,
47 		       to_pathname, flags);
48 }
49 
50 FIXTURE(file_stressor) {
51 	int fd_tmpfs;
52 	int nr_procs;
53 	int max_fds;
54 	pid_t *pids_openers;
55 	pid_t *pids_getdents;
56 	int *fd_proc_pid;
57 };
58 
59 FIXTURE_SETUP(file_stressor)
60 {
61 	int fd_context;
62 
63 	ASSERT_EQ(unshare(CLONE_NEWNS), 0);
64 	ASSERT_EQ(mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL), 0);
65 	ASSERT_EQ(mkdir("/slab_typesafe_by_rcu", 0755), 0);
66 
67 	fd_context = sys_fsopen("tmpfs", 0);
68 	ASSERT_GE(fd_context, 0);
69 
70 	ASSERT_EQ(sys_fsconfig(fd_context, FSCONFIG_CMD_CREATE, NULL, NULL, 0), 0);
71 	self->fd_tmpfs = sys_fsmount(fd_context, 0, 0);
72 	ASSERT_GE(self->fd_tmpfs, 0);
73 	ASSERT_EQ(close(fd_context), 0);
74 
75 	ASSERT_EQ(sys_move_mount(self->fd_tmpfs, "", -EBADF, "/slab_typesafe_by_rcu", MOVE_MOUNT_F_EMPTY_PATH), 0);
76 
77 	self->nr_procs = sysconf(_SC_NPROCESSORS_ONLN);
78 	self->pids_openers = malloc(sizeof(pid_t) * self->nr_procs);
79 	ASSERT_NE(self->pids_openers, NULL);
80 	self->pids_getdents = malloc(sizeof(pid_t) * self->nr_procs);
81 	ASSERT_NE(self->pids_getdents, NULL);
82 	self->fd_proc_pid = malloc(sizeof(int) * self->nr_procs);
83 	ASSERT_NE(self->fd_proc_pid, NULL);
84 	self->max_fds = 500;
85 }
86 
87 FIXTURE_TEARDOWN(file_stressor)
88 {
89 	for (int i = 0; i < self->nr_procs; i++) {
90 		int wstatus;
91 		pid_t pid;
92 
93 		pid = waitpid(self->pids_openers[i], &wstatus, 0);
94 		ASSERT_EQ(pid, self->pids_openers[i]);
95 		ASSERT_TRUE(!WIFEXITED(wstatus) || !WIFSIGNALED(wstatus));
96 
97 		pid = waitpid(self->pids_getdents[i], &wstatus, 0);
98 		ASSERT_EQ(pid, self->pids_getdents[i]);
99 		ASSERT_TRUE(!WIFEXITED(wstatus) || !WIFSIGNALED(wstatus));
100 	}
101 	free(self->pids_openers);
102 	free(self->pids_getdents);
103 	ASSERT_EQ(close(self->fd_tmpfs), 0);
104 
105 	umount2("/slab_typesafe_by_rcu", 0);
106 	ASSERT_EQ(rmdir("/slab_typesafe_by_rcu"), 0);
107 }
108 
109 TEST_F_TIMEOUT(file_stressor, slab_typesafe_by_rcu, 900 * 2)
110 {
111 	for (int i = 0; i < self->nr_procs; i++) {
112 		pid_t pid_self;
113 
114 		self->pids_openers[i] = fork();
115 		ASSERT_GE(self->pids_openers[i], 0);
116 
117 		if (self->pids_openers[i] != 0)
118 			continue;
119 
120 		self->pids_openers[i] = getpid();
121 		for (;;) {
122 			for (int i = 0; i < self->max_fds; i++) {
123 				char path[PATH_MAX];
124 				int fd;
125 
126 				sprintf(path, "/slab_typesafe_by_rcu/file-%d-%d", self->pids_openers[i], i);
127 				fd = open(path, O_CREAT | O_RDONLY | O_CLOEXEC, 0644);
128 				if (fd < 0)
129 					continue;
130 			}
131 
132 			close_range(3, ~0U, 0);
133 		}
134 
135 		exit(0);
136 	}
137 
138 	for (int i = 0; i < self->nr_procs; i++) {
139 		char path[PATH_MAX];
140 
141 		sprintf(path, "/proc/%d/fd/", self->pids_openers[i]);
142 		self->fd_proc_pid[i] = open(path, O_DIRECTORY | O_RDONLY | O_CLOEXEC);
143 		ASSERT_GE(self->fd_proc_pid[i], 0);
144 	}
145 
146 	for (int i = 0; i < self->nr_procs; i++) {
147 		self->pids_getdents[i] = fork();
148 		ASSERT_GE(self->pids_getdents[i], 0);
149 
150 		if (self->pids_getdents[i] != 0)
151 			continue;
152 
153 		self->pids_getdents[i] = getpid();
154 		for (;;) {
155 			char ents[1024];
156 			ssize_t nr_read;
157 
158 			/*
159 			 * Concurrently read /proc/<pid>/fd/ which rougly does:
160 			 *
161 			 * f = fget_task_next(p, &fd);
162 			 * if (!f)
163 			 *	break;
164 			 * data.mode = f->f_mode;
165 			 * fput(f);
166 			 *
167 			 * Which means that it'll try to get a reference to a
168 			 * file in another task's file descriptor table.
169 			 *
170 			 * Under heavy file load it is increasingly likely that
171 			 * the other task will manage to close @file and @file
172 			 * is being recycled due to SLAB_TYPEAFE_BY_RCU
173 			 * concurrently. This will trigger various warnings in
174 			 * the file reference counting code.
175 			 */
176 			do {
177 				nr_read = syscall(SYS_getdents64, self->fd_proc_pid[i], ents, sizeof(ents));
178 			} while (nr_read >= 0);
179 
180 			lseek(self->fd_proc_pid[i], 0, SEEK_SET);
181 		}
182 
183 		exit(0);
184 	}
185 
186 	ASSERT_EQ(clock_nanosleep(CLOCK_MONOTONIC, 0, &(struct timespec){ .tv_sec = 900 /* 15 min */ }, NULL), 0);
187 
188 	for (int i = 0; i < self->nr_procs; i++) {
189 		kill(self->pids_openers[i], SIGKILL);
190 		kill(self->pids_getdents[i], SIGKILL);
191 	}
192 }
193 
194 TEST_HARNESS_MAIN
195