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