1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright 2020 Google LLC
4 */
5 #include <errno.h>
6 #include <fcntl.h>
7 #include <getopt.h>
8 #include <pthread.h>
9 #include <stdbool.h>
10 #include <stdint.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <sys/mount.h>
15 #include <sys/stat.h>
16 #include <unistd.h>
17
18 #include "utils.h"
19
20 #define err_msg(...) \
21 do { \
22 fprintf(stderr, "%s: (%d) ", TAG, __LINE__); \
23 fprintf(stderr, __VA_ARGS__); \
24 fprintf(stderr, " (%s)\n", strerror(errno)); \
25 } while (false)
26
27 #define TAG "incfs_stress"
28
29 struct options {
30 bool no_cleanup; /* -c */
31 const char *test_dir; /* -d */
32 unsigned int rng_seed; /* -g */
33 int num_reads; /* -n */
34 int readers; /* -r */
35 int size; /* -s */
36 int timeout; /* -t */
37 };
38
39 struct read_data {
40 const char *filename;
41 int dir_fd;
42 size_t filesize;
43 int num_reads;
44 unsigned int rng_seed;
45 };
46
47 int cancel_threads;
48
parse_options(int argc,char * const * argv,struct options * options)49 int parse_options(int argc, char *const *argv, struct options *options)
50 {
51 signed char c;
52
53 /* Set defaults here */
54 *options = (struct options){
55 .test_dir = ".",
56 .num_reads = 1000,
57 .readers = 10,
58 .size = 10,
59 };
60
61 /* Load options from command line here */
62 while ((c = getopt(argc, argv, "cd:g:n:r:s:t:")) != -1) {
63 switch (c) {
64 case 'c':
65 options->no_cleanup = true;
66 break;
67
68 case 'd':
69 options->test_dir = optarg;
70 break;
71
72 case 'g':
73 options->rng_seed = strtol(optarg, NULL, 10);
74 break;
75
76 case 'n':
77 options->num_reads = strtol(optarg, NULL, 10);
78 break;
79
80 case 'r':
81 options->readers = strtol(optarg, NULL, 10);
82 break;
83
84 case 's':
85 options->size = strtol(optarg, NULL, 10);
86 break;
87
88 case 't':
89 options->timeout = strtol(optarg, NULL, 10);
90 break;
91 }
92 }
93
94 return 0;
95 }
96
reader(void * data)97 void *reader(void *data)
98 {
99 struct read_data *read_data = (struct read_data *)data;
100 int i;
101 int fd = -1;
102 void *buffer = malloc(read_data->filesize);
103
104 if (!buffer) {
105 err_msg("Failed to alloc read buffer");
106 goto out;
107 }
108
109 fd = openat(read_data->dir_fd, read_data->filename,
110 O_RDONLY | O_CLOEXEC);
111 if (fd == -1) {
112 err_msg("Failed to open file");
113 goto out;
114 }
115
116 for (i = 0; i < read_data->num_reads && !cancel_threads; ++i) {
117 off_t offset = rnd(read_data->filesize, &read_data->rng_seed);
118 size_t count =
119 rnd(read_data->filesize - offset, &read_data->rng_seed);
120 ssize_t err = pread(fd, buffer, count, offset);
121
122 if (err != count)
123 err_msg("failed to read with value %lu", err);
124 }
125
126 out:
127 close(fd);
128 free(read_data);
129 free(buffer);
130 return NULL;
131 }
132
write_data(int cmd_fd,int dir_fd,const char * name,size_t size)133 int write_data(int cmd_fd, int dir_fd, const char *name, size_t size)
134 {
135 int fd = openat(dir_fd, name, O_RDWR | O_CLOEXEC);
136 struct incfs_permit_fill permit_fill = {
137 .file_descriptor = fd,
138 };
139 int error = 0;
140 int i;
141 int block_count = 1 + (size - 1) / INCFS_DATA_FILE_BLOCK_SIZE;
142
143 if (fd == -1) {
144 err_msg("Could not open file for writing %s", name);
145 return -errno;
146 }
147
148 if (ioctl(cmd_fd, INCFS_IOC_PERMIT_FILL, &permit_fill)) {
149 err_msg("Failed to call PERMIT_FILL");
150 error = -errno;
151 goto out;
152 }
153
154 for (i = 0; i < block_count; ++i) {
155 uint8_t data[INCFS_DATA_FILE_BLOCK_SIZE] = {};
156 size_t block_size =
157 size > i * INCFS_DATA_FILE_BLOCK_SIZE ?
158 INCFS_DATA_FILE_BLOCK_SIZE :
159 size - (i * INCFS_DATA_FILE_BLOCK_SIZE);
160 struct incfs_fill_block fill_block = {
161 .compression = COMPRESSION_NONE,
162 .block_index = i,
163 .data_len = block_size,
164 .data = ptr_to_u64(data),
165 };
166 struct incfs_fill_blocks fill_blocks = {
167 .count = 1,
168 .fill_blocks = ptr_to_u64(&fill_block),
169 };
170 int written = ioctl(fd, INCFS_IOC_FILL_BLOCKS, &fill_blocks);
171
172 if (written != 1) {
173 error = -errno;
174 err_msg("Failed to write block %d in file %s", i, name);
175 break;
176 }
177 }
178 out:
179 close(fd);
180 return error;
181 }
182
test_files(int src_dir,int dst_dir,struct options const * options)183 int test_files(int src_dir, int dst_dir, struct options const *options)
184 {
185 unsigned int seed = options->rng_seed;
186 int cmd_file = openat(dst_dir, INCFS_PENDING_READS_FILENAME,
187 O_RDONLY | O_CLOEXEC);
188 int err;
189 const char *name = "001";
190 incfs_uuid_t id;
191 size_t size;
192 int i;
193 pthread_t *threads = NULL;
194
195 size = 1 << (rnd(options->size, &seed) + 12);
196 size += rnd(size, &seed);
197
198 if (cmd_file == -1) {
199 err_msg("Could not open command file");
200 return -errno;
201 }
202
203 err = emit_file(cmd_file, NULL, name, &id, size, NULL);
204 if (err) {
205 err_msg("Failed to create file %s", name);
206 return err;
207 }
208
209 threads = malloc(sizeof(pthread_t) * options->readers);
210 if (!threads) {
211 err_msg("Could not allocate memory for threads");
212 return -ENOMEM;
213 }
214
215 for (i = 0; i < options->readers; ++i) {
216 struct read_data *read_data = malloc(sizeof(*read_data));
217
218 if (!read_data) {
219 err_msg("Failed to allocate read_data");
220 err = -ENOMEM;
221 break;
222 }
223
224 *read_data = (struct read_data){
225 .filename = name,
226 .dir_fd = dst_dir,
227 .filesize = size,
228 .num_reads = options->num_reads,
229 .rng_seed = seed,
230 };
231
232 rnd(0, &seed);
233
234 err = pthread_create(threads + i, 0, reader, read_data);
235 if (err) {
236 err_msg("Failed to create thread");
237 free(read_data);
238 break;
239 }
240 }
241
242 if (err)
243 cancel_threads = 1;
244 else
245 err = write_data(cmd_file, dst_dir, name, size);
246
247 for (; i > 0; --i) {
248 if (pthread_join(threads[i - 1], NULL)) {
249 err_msg("FATAL: failed to join thread");
250 exit(-errno);
251 }
252 }
253
254 free(threads);
255 close(cmd_file);
256 return err;
257 }
258
main(int argc,char * const * argv)259 int main(int argc, char *const *argv)
260 {
261 struct options options;
262 int err;
263 const char *src_dir = "src";
264 const char *dst_dir = "dst";
265 int src_dir_fd = -1;
266 int dst_dir_fd = -1;
267
268 err = parse_options(argc, argv, &options);
269 if (err)
270 return err;
271
272 err = chdir(options.test_dir);
273 if (err) {
274 err_msg("Failed to change to %s", options.test_dir);
275 return -errno;
276 }
277
278 err = remove_dir(src_dir) || remove_dir(dst_dir);
279 if (err)
280 return err;
281
282 err = mkdir(src_dir, 0700);
283 if (err) {
284 err_msg("Failed to make directory %s", src_dir);
285 err = -errno;
286 goto cleanup;
287 }
288
289 err = mkdir(dst_dir, 0700);
290 if (err) {
291 err_msg("Failed to make directory %s", src_dir);
292 err = -errno;
293 goto cleanup;
294 }
295
296 err = mount_fs(dst_dir, src_dir, options.timeout);
297 if (err) {
298 err_msg("Failed to mount incfs");
299 goto cleanup;
300 }
301
302 src_dir_fd = open(src_dir, O_RDONLY | O_CLOEXEC);
303 dst_dir_fd = open(dst_dir, O_RDONLY | O_CLOEXEC);
304 if (src_dir_fd == -1 || dst_dir_fd == -1) {
305 err_msg("Failed to open src or dst dir");
306 err = -errno;
307 goto cleanup;
308 }
309
310 err = test_files(src_dir_fd, dst_dir_fd, &options);
311
312 cleanup:
313 close(src_dir_fd);
314 close(dst_dir_fd);
315 if (!options.no_cleanup) {
316 umount(dst_dir);
317 remove_dir(dst_dir);
318 remove_dir(src_dir);
319 }
320
321 return err;
322 }
323