1 /*
2  * SPDX-License-Identifier: MIT
3  *
4  * Copyright (c) 2023, Rob Norris <robn@despairlabs.com>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to
8  * deal in the Software without restriction, including without limitation the
9  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10  * sell copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22  * IN THE SOFTWARE.
23  *
24  */
25 
26 #ifndef	_GNU_SOURCE
27 #define	_GNU_SOURCE
28 #endif
29 
30 #include <fcntl.h>
31 #include <unistd.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <errno.h>
36 #include <sys/stat.h>
37 #include <sys/wait.h>
38 
39 #define	DATASIZE	(4096)
40 char data[DATASIZE];
41 
42 static int
43 _open_file(int n, int wr)
44 {
45 	char buf[256];
46 	int fd;
47 
48 	snprintf(buf, sizeof (buf), "testdata_%d_%d", getpid(), n);
49 
50 	if ((fd = open(buf, wr ? (O_WRONLY | O_CREAT) : O_RDONLY,
51 	    wr ? (S_IRUSR | S_IWUSR) : 0)) < 0) {
52 		fprintf(stderr, "Error: open '%s' (%s): %s\n",
53 		    buf, wr ? "write" : "read", strerror(errno));
54 		exit(1);
55 	}
56 
57 	return (fd);
58 }
59 
60 static void
61 _write_file(int n, int fd)
62 {
63 	/* write a big ball of stuff */
64 	ssize_t nwr = write(fd, data, DATASIZE);
65 	if (nwr < 0) {
66 		fprintf(stderr, "Error: write '%d_%d': %s\n",
67 		    getpid(), n, strerror(errno));
68 		exit(1);
69 	} else if (nwr < DATASIZE) {
70 		fprintf(stderr, "Error: write '%d_%d': short write\n", getpid(),
71 		    n);
72 		exit(1);
73 	}
74 }
75 
76 static int
77 _seek_file(int n, int fd)
78 {
79 	struct stat st;
80 	if (fstat(fd, &st) < 0) {
81 		fprintf(stderr, "Error: fstat '%d_%d': %s\n", getpid(), n,
82 		    strerror(errno));
83 		exit(1);
84 	}
85 
86 	/*
87 	 * A zero-sized file correctly has no data, so seeking the file is
88 	 * pointless.
89 	 */
90 	if (st.st_size == 0)
91 		return (0);
92 
93 	/* size is real, and we only write, so SEEK_DATA must find something */
94 	if (lseek(fd, 0, SEEK_DATA) < 0) {
95 		if (errno == ENXIO)
96 			return (1);
97 		fprintf(stderr, "Error: lseek '%d_%d': %s\n",
98 		    getpid(), n, strerror(errno));
99 		exit(2);
100 	}
101 
102 	return (0);
103 }
104 
105 int
106 main(int argc, char **argv)
107 {
108 	int nfiles = 0;
109 	int nthreads = 0;
110 
111 	if (argc < 3 || (nfiles = atoi(argv[1])) == 0 ||
112 	    (nthreads = atoi(argv[2])) == 0) {
113 		printf("usage: seekflood <nfiles> <threads>\n");
114 		exit(1);
115 	}
116 
117 	memset(data, 0x5a, DATASIZE);
118 
119 	/* fork off some flood threads */
120 	for (int i = 0; i < nthreads; i++) {
121 		if (!fork()) {
122 			/* thread main */
123 
124 			/* create zero file */
125 			int fd = _open_file(0, 1);
126 			_write_file(0, fd);
127 			close(fd);
128 
129 			int count = 0;
130 
131 			int h = 0, i, j, rfd, wfd;
132 			for (i = 0; i < nfiles; i += 2, h++) {
133 				j = i+1;
134 
135 				/* seek h, write i */
136 				rfd = _open_file(h, 0);
137 				wfd = _open_file(i, 1);
138 				count += _seek_file(h, rfd);
139 				_write_file(i, wfd);
140 				close(rfd);
141 				close(wfd);
142 
143 				/* seek i, write j */
144 				rfd = _open_file(i, 0);
145 				wfd = _open_file(j, 1);
146 				count += _seek_file(i, rfd);
147 				_write_file(j, wfd);
148 				close(rfd);
149 				close(wfd);
150 			}
151 
152 			/* return count of failed seeks to parent */
153 			exit(count < 256 ? count : 255);
154 		}
155 	}
156 
157 	/* wait for threads, take their seek fail counts from exit code */
158 	int count = 0, crashed = 0;
159 	for (int i = 0; i < nthreads; i++) {
160 		int wstatus;
161 		wait(&wstatus);
162 		if (WIFEXITED(wstatus))
163 			count += WEXITSTATUS(wstatus);
164 		else
165 			crashed++;
166 	}
167 
168 	if (crashed) {
169 		fprintf(stderr, "Error: child crashed; test failed\n");
170 		exit(1);
171 	}
172 
173 	if (count) {
174 		fprintf(stderr, "Error: %d seek failures; test failed\n",
175 		    count);
176 		exit(1);
177 	}
178 
179 	exit(0);
180 }
181