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,	"lockf N",	  "start N workers locking a single file via lockf" },
29 	{ NULL,	"lockf-ops N",	  "stop after N lockf bogo operations" },
30 	{ NULL, "lockf-nonblock", "don't block if lock cannot be obtained, re-try" },
31 	{ NULL, NULL,		NULL }
32 };
33 
34 #if defined(HAVE_LOCKF)
35 
36 #define LOCK_FILE_SIZE	(64 * 1024)
37 #define LOCK_SIZE	(8)
38 #define LOCK_MAX	(1024)
39 
40 typedef struct lockf_info {
41 	off_t	offset;
42 	struct lockf_info *next;
43 } stress_lockf_info_t;
44 
45 typedef struct {
46 	stress_lockf_info_t *head;	/* Head of lockf_info procs list */
47 	stress_lockf_info_t *tail;	/* Tail of lockf_info procs list */
48 	stress_lockf_info_t *free;	/* List of free'd lockf_infos */
49 	uint64_t length;		/* Length of list */
50 } stress_lockf_info_list_t;
51 
52 static stress_lockf_info_list_t lockf_infos;
53 #endif
54 
stress_lockf_set_nonblock(const char * opt)55 static int stress_lockf_set_nonblock(const char *opt)
56 {
57 	bool lockf_nonblock = true;
58 
59 	(void)opt;
60 	return stress_set_setting("lockf-nonblock", TYPE_ID_BOOL, &lockf_nonblock);
61 }
62 
63 static const stress_opt_set_func_t opt_set_funcs[] = {
64 	{ OPT_lockf_nonblock,   stress_lockf_set_nonblock },
65 	{ 0,			NULL }
66 };
67 
68 #if defined(HAVE_LOCKF)
69 /*
70  *  stress_lockf_info_new()
71  *	allocate a new lockf_info, add to end of list
72  */
stress_lockf_info_new(void)73 static stress_lockf_info_t *stress_lockf_info_new(void)
74 {
75 	stress_lockf_info_t *new;
76 
77 	if (lockf_infos.free) {
78 		/* Pop an old one off the free list */
79 		new = lockf_infos.free;
80 		lockf_infos.free = new->next;
81 		new->next = NULL;
82 	} else {
83 		new = calloc(1, sizeof(*new));
84 		if (!new)
85 			return NULL;
86 	}
87 
88 	if (lockf_infos.head)
89 		lockf_infos.tail->next = new;
90 	else
91 		lockf_infos.head = new;
92 
93 	lockf_infos.tail = new;
94 	lockf_infos.length++;
95 
96 	return new;
97 }
98 
99 /*
100  *  stress_lockf_info_head_remove
101  *	reap a lockf_info and remove a lockf_info from head of list, put it onto
102  *	the free lockf_info list
103  */
stress_lockf_info_head_remove(void)104 static void stress_lockf_info_head_remove(void)
105 {
106 	if (lockf_infos.head) {
107 		stress_lockf_info_t *head = lockf_infos.head;
108 
109 		if (lockf_infos.tail == lockf_infos.head) {
110 			lockf_infos.tail = NULL;
111 			lockf_infos.head = NULL;
112 		} else {
113 			lockf_infos.head = head->next;
114 		}
115 
116 		/* Shove it on the free list */
117 		head->next = lockf_infos.free;
118 		lockf_infos.free = head;
119 
120 		lockf_infos.length--;
121 	}
122 }
123 
124 /*
125  *  stress_lockf_info_free()
126  *	free the lockf_infos off the lockf_info head and free lists
127  */
stress_lockf_info_free(void)128 static void stress_lockf_info_free(void)
129 {
130 	while (lockf_infos.head) {
131 		stress_lockf_info_t *next = lockf_infos.head->next;
132 
133 		free(lockf_infos.head);
134 		lockf_infos.head = next;
135 	}
136 
137 	while (lockf_infos.free) {
138 		stress_lockf_info_t *next = lockf_infos.free->next;
139 
140 		free(lockf_infos.free);
141 		lockf_infos.free = next;
142 	}
143 }
144 
145 /*
146  *  stress_lockf_unlock()
147  *	pop oldest lock record off list and unlock it
148  */
stress_lockf_unlock(const stress_args_t * args,const int fd)149 static int stress_lockf_unlock(const stress_args_t *args, const int fd)
150 {
151 	/* Pop one off list */
152 	if (!lockf_infos.head)
153 		return 0;
154 
155 	if (lseek(fd, lockf_infos.head->offset, SEEK_SET) < 0) {
156 		pr_fail("%s: lseek failed, errno=%d (%s)\n",
157 			args->name, errno, strerror(errno));
158 		return -1;
159 	}
160 	stress_lockf_info_head_remove();
161 
162 	if (lockf(fd, F_ULOCK, LOCK_SIZE) < 0) {
163 		pr_fail("%s: lockf F_ULOCK failed, errno=%d (%s)\n",
164 			args->name, errno, strerror(errno));
165 		return -1;
166 	}
167 	return 0;
168 }
169 
170 /*
171  *  stress_lockf_contention()
172  *	hammer lock/unlock to create some file lock contention
173  */
stress_lockf_contention(const stress_args_t * args,const int fd,const int bad_fd)174 static int stress_lockf_contention(
175 	const stress_args_t *args,
176 	const int fd,
177 	const int bad_fd)
178 {
179 	bool lockf_nonblock = false;
180 	int lockf_cmd;
181 	int counter = 0;
182 
183 	(void)stress_get_setting("lockf-nonblock", &lockf_nonblock);
184 	lockf_cmd = lockf_nonblock ?  F_TLOCK : F_LOCK;
185 	stress_mwc_reseed();
186 
187 	do {
188 		off_t offset;
189 		int rc;
190 		stress_lockf_info_t *lockf_info;
191 
192 		if (lockf_infos.length >= LOCK_MAX)
193 			if (stress_lockf_unlock(args, fd) < 0)
194 				return -1;
195 
196 		offset = stress_mwc64() % (LOCK_FILE_SIZE - LOCK_SIZE);
197 		if (lseek(fd, offset, SEEK_SET) < 0) {
198 			pr_fail("%s: lseek failed, errno=%d (%s)\n",
199 				args->name, errno, strerror(errno));
200 			return -1;
201 		}
202 		rc = lockf(fd, lockf_cmd, LOCK_SIZE);
203 		if (rc < 0) {
204 			if (stress_lockf_unlock(args, fd) < 0)
205 				return -1;
206 			continue;
207 		}
208 
209 		/* Locked OK, add to lock list */
210 		lockf_info = stress_lockf_info_new();
211 		if (!lockf_info) {
212 			pr_fail("%s: calloc failed, out of memory\n", args->name);
213 			return -1;
214 		}
215 		lockf_info->offset = offset;
216 		/*
217 		 *  Occasionally exercise lock on a bad fd, ignore error
218 		 */
219 		if (counter++ >= 65536) {
220 			rc = lockf(bad_fd, lockf_cmd, LOCK_SIZE);
221 			(void)rc;
222 			counter = 0;
223 
224 			/* Exercise F_TEST, ignore result */
225 			rc = lockf(fd, F_TEST, LOCK_SIZE);
226 			(void)rc;
227 		}
228 
229 
230 		inc_counter(args);
231 	} while (keep_stressing(args));
232 
233 	return 0;
234 }
235 
236 /*
237  *  stress_lockf
238  *	stress file locking via lockf()
239  */
stress_lockf(const stress_args_t * args)240 static int stress_lockf(const stress_args_t *args)
241 {
242 	int fd, ret = EXIT_FAILURE;
243 	const int bad_fd = stress_get_bad_fd();
244 	pid_t cpid = -1;
245 	char filename[PATH_MAX];
246 	char pathname[PATH_MAX];
247 	char buffer[4096];
248 	off_t offset;
249 	ssize_t rc;
250 
251 	(void)memset(buffer, 0, sizeof(buffer));
252 
253 	/*
254 	 *  There will be a race to create the directory
255 	 *  so EEXIST is expected on all but one instance
256 	 */
257 	(void)stress_temp_dir_args(args, pathname, sizeof(pathname));
258 	if (mkdir(pathname, S_IRWXU) < 0) {
259 		if (errno != EEXIST) {
260 			ret = exit_status(errno);
261 			pr_fail("%s: mkdir %s failed, errno=%d (%s)\n",
262 				args->name, pathname, errno, strerror(errno));
263 			return ret;
264 		}
265 	}
266 
267 	/*
268 	 *  Lock file is based on parent pid and instance 0
269 	 *  as we need to share this among all the other
270 	 *  stress flock processes
271 	 */
272 	(void)stress_temp_filename_args(args,
273 		filename, sizeof(filename), stress_mwc32());
274 
275 	if ((fd = open(filename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)) < 0) {
276 		ret = exit_status(errno);
277 		pr_fail("%s: open %s failed, errno=%d (%s)\n",
278 			args->name, filename, errno, strerror(errno));
279 		(void)rmdir(pathname);
280 		return ret;
281 	}
282 
283 	if (lseek(fd, 0, SEEK_SET) < 0) {
284 		pr_fail("%s: lseek failed, errno=%d (%s)\n",
285 			args->name, errno, strerror(errno));
286 		goto tidy;
287 	}
288 	for (offset = 0; offset < LOCK_FILE_SIZE; offset += sizeof(buffer)) {
289 redo:
290 		if (!keep_stressing_flag()) {
291 			ret = EXIT_SUCCESS;
292 			goto tidy;
293 		}
294 		rc = write(fd, buffer, sizeof(buffer));
295 		if ((rc < 0) || (rc != sizeof(buffer))) {
296 			if ((errno == EAGAIN) || (errno == EINTR))
297 				goto redo;
298 			ret = exit_status(errno);
299 			pr_fail("%s: write failed, errno=%d (%s)\n",
300 				args->name, errno, strerror(errno));
301 			goto tidy;
302 		}
303 	}
304 
305 	stress_set_proc_state(args->name, STRESS_STATE_RUN);
306 again:
307 	cpid = fork();
308 	if (cpid < 0) {
309 		if (stress_redo_fork(errno))
310 			goto again;
311 		if (!keep_stressing(args))
312 			goto tidy;
313 		pr_fail("%s: fork failed, errno=%d (%s)\n",
314 			args->name, errno, strerror(errno));
315 		goto tidy;
316 	}
317 	if (cpid == 0) {
318 		(void)setpgid(0, g_pgrp);
319 		stress_parent_died_alarm();
320 		(void)sched_settings_apply(true);
321 
322 		if (stress_lockf_contention(args, fd, bad_fd) < 0)
323 			_exit(EXIT_FAILURE);
324 		stress_lockf_info_free();
325 		_exit(EXIT_SUCCESS);
326 	}
327 	(void)setpgid(cpid, g_pgrp);
328 
329 	if (stress_lockf_contention(args, fd, bad_fd) == 0)
330 		ret = EXIT_SUCCESS;
331 tidy:
332 	stress_set_proc_state(args->name, STRESS_STATE_DEINIT);
333 
334 	if (cpid > 0) {
335 		int status;
336 
337 		(void)kill(cpid, SIGKILL);
338 		(void)shim_waitpid(cpid, &status, 0);
339 	}
340 	stress_lockf_info_free();
341 
342 	(void)close(fd);
343 	(void)unlink(filename);
344 	(void)rmdir(pathname);
345 
346 	return ret;
347 }
348 
349 stressor_info_t stress_lockf_info = {
350 	.stressor = stress_lockf,
351 	.class = CLASS_FILESYSTEM | CLASS_OS,
352 	.opt_set_funcs = opt_set_funcs,
353 	.help = help
354 };
355 #else
356 stressor_info_t stress_lockf_info = {
357 	.stressor = stress_not_implemented,
358 	.class = CLASS_FILESYSTEM | CLASS_OS,
359 	.opt_set_funcs = opt_set_funcs,
360 	.help = help
361 };
362 #endif
363