add fstest.c and mmapcat.c - keep a copy of these here so they don't get
[xfstests-dev.git] / src / fstest.c
1 /* Verification tool, designed to detect data corruption on a filesystem
2
3    tridge@samba.org, March 2002
4  */
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <unistd.h>
9 #include <sys/stat.h>
10 #include <sys/wait.h>
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <sys/mman.h>
14 #include <dirent.h>
15 #include <fcntl.h>
16 #include <errno.h>
17
18 /* variables settable on the command line */
19 static int loop_count = 100;
20 static int num_files = 1;
21 static int file_size = 1024*1024;
22 static int block_size = 1024;
23 static char *base_dir = ".";
24 static int use_mmap;
25 static int use_sync;
26 static int do_frags = 1;
27
28 typedef unsigned char uchar;
29
30 #ifndef MIN
31 #define MIN(a,b) ((a)<(b)?(a):(b))
32 #endif
33
34 static void *x_malloc(int size)
35 {
36         void *ret = malloc(size);
37         if (!ret) {
38                 fprintf(stderr,"Out of memory for size %d!\n", size);
39                 exit(1);
40         }
41         return ret;
42 }
43
44
45 /* generate a buffer for a particular child, fnum etc. Just use a simple buffer
46    to make debugging easy 
47 */
48 static void gen_buffer(uchar *buf, int loop, int child, int fnum, int ofs)
49 {
50         uchar v = (loop+child+fnum+(ofs/block_size)) % 256;
51         memset(buf, v, block_size);
52 }
53
54 /* 
55    check if a buffer from disk is correct
56 */
57 static void check_buffer(uchar *buf, int loop, int child, int fnum, int ofs)
58 {
59         uchar *buf2;
60
61         buf2 = x_malloc(block_size);
62
63         gen_buffer(buf2, loop, child, fnum, ofs);
64         
65         if (memcmp(buf, buf2, block_size) != 0) {
66                 int i, j;
67                 for (i=0;buf[i] == buf2[i] && i<block_size;i++) ;
68                 fprintf(stderr,"Corruption in child %d fnum %d at offset %d\n",
69                         child, fnum, ofs+i);
70
71                 printf("Correct:   ");
72                 for (j=0;j<MIN(20, block_size-i);j++) {
73                         printf("%02x ", buf2[j+i]);
74                 }
75                 printf("\n");
76
77                 printf("Incorrect: ");
78                 for (j=0;j<MIN(20, block_size-i);j++) {
79                         printf("%02x ", buf[j+i]);
80                 }
81                 printf("\n");
82                 exit(1);
83         }
84
85         free(buf2);
86 }
87
88 /*
89   create a file with a known data set for a child
90  */
91 static void create_file(const char *dir, int loop, int child, int fnum)
92 {
93         uchar *buf;
94         int size, fd;
95         char fname[1024];
96
97         buf = x_malloc(block_size);
98         sprintf(fname, "%s/file%d", dir, fnum);
99         fd = open(fname, O_RDWR|O_CREAT|O_TRUNC | (use_sync?O_SYNC:0), 0644);
100         if (fd == -1) {
101                 perror(fname);
102                 exit(1);
103         }
104                 
105         if (!use_mmap) {
106                 for (size=0; size<file_size; size += block_size * do_frags) {
107                         gen_buffer(buf, loop, child, fnum, size);
108                         if (pwrite(fd, buf, block_size, size) != block_size) {
109                                 fprintf(stderr,"Write failed at offset %d\n", size);
110                                 exit(1);
111                         }
112                 }
113         } else {
114                 char *p;
115                 if (ftruncate(fd, file_size) != 0) {
116                         perror("ftruncate");
117                         exit(1);
118                 }
119                 p = mmap(NULL, file_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
120                 if (p == (char *)-1) {
121                         perror("mmap");
122                         exit(1);
123                 }
124                 for (size=0; size<file_size; size += block_size * do_frags) {
125                         gen_buffer(p+size, loop, child, fnum, size);
126                 }
127                 munmap(p, file_size);
128         }
129
130         free(buf);
131         close(fd);
132 }
133
134 /* 
135    check that a file has the right data
136  */
137 static void check_file(const char *dir, int loop, int child, int fnum)
138 {
139         uchar *buf;
140         int size, fd;
141         char fname[1024];
142
143         buf = x_malloc(block_size);
144
145         sprintf(fname, "%s/file%d", dir, fnum);
146         fd = open(fname, O_RDONLY);
147         if (fd == -1) {
148                 perror(fname);
149                 exit(1);
150         }
151
152         for (size=0; size<file_size; size += block_size * do_frags) {
153                 if (pread(fd, buf, block_size, size) != block_size) {
154                         fprintf(stderr,"read failed at offset %d\n", size);
155                         exit(1);
156                 }
157                 check_buffer(buf, loop, child, fnum, size);
158         }
159
160         free(buf);
161         close(fd);
162 }
163
164 /* 
165    recursive directory traversal - used for cleanup
166    fn() is called on all files/dirs in the tree
167  */
168 void traverse(const char *dir, int (*fn)(const char *))
169 {
170         DIR *d;
171         struct dirent *de;
172
173         d = opendir(dir);
174         if (!d) return;
175
176         while ((de = readdir(d))) {
177                 char fname[1024];
178                 struct stat st;
179
180                 if (strcmp(de->d_name,".") == 0) continue;
181                 if (strcmp(de->d_name,"..") == 0) continue;
182
183                 sprintf(fname, "%s/%s", dir, de->d_name);
184                 if (lstat(fname, &st)) {
185                         perror(fname);
186                         continue;
187                 }
188
189                 if (S_ISDIR(st.st_mode)) {
190                         traverse(fname, fn);
191                 }
192
193                 fn(fname);
194         }
195
196         closedir(d);
197 }
198
199 /* the main child function - this creates/checks the file for one child */
200 static void run_child(int child)
201 {
202         int i, loop;
203         char dir[1024];
204
205         sprintf(dir, "%s/child%d", base_dir, child);
206
207         /* cleanup any old files */
208         if (remove(dir) != 0 && errno != ENOENT) {
209                 printf("Child %d cleaning %s\n", child, dir);
210                 traverse(dir, remove);
211                 remove(dir);
212         }
213
214         if (mkdir(dir, 0755) != 0) {
215                 perror(dir);
216                 exit(1);
217         }
218
219         for (loop = 0; loop < loop_count; loop++) {
220                 printf("Child %d loop %d\n", child, loop);
221                 for (i=0;i<num_files;i++) {
222                         create_file(dir, loop, child, i);
223                 }
224                 for (i=0;i<num_files;i++) {
225                         check_file(dir, loop, child, i);
226                 }
227         }
228
229         /* cleanup afterwards */
230         printf("Child %d cleaning up %s\n", child, dir);
231         traverse(dir, remove);
232         remove(dir);
233
234         exit(0);
235 }
236
237 static void usage(void)
238 {
239         printf("\n"
240 "Usage: fstest [options]\n"
241 "\n"
242 " -F                    generate files with holes\n"
243 " -n num_children       set number of child processes\n"
244 " -f num_files          set number of files\n"
245 " -s file_size          set file sizes\n"
246 " -b block_size         set block (IO) size\n"
247 " -p path               set base path\n"
248 " -l loops              set loop count\n"
249 " -m                    use mmap\n"
250 " -S                    use synchronous IO\n"
251 " -h                    show this help message\n");
252 }
253
254 /* main program */
255 int main(int argc, char *argv[])
256 {
257         int c;
258         extern char *optarg;
259         extern int optind;
260         int num_children = 1;
261         int i, status, ret;
262
263         while ((c = getopt(argc, argv, "Fn:s:f:p:l:b:Shm")) != -1) {
264                 switch (c) {
265                 case 'F':
266                         do_frags = 2;
267                         break;
268                 case 'n':
269                         num_children = strtol(optarg, NULL, 0);
270                         break;
271                 case 'b':
272                         block_size = strtol(optarg, NULL, 0);
273                         break;
274                 case 'f':
275                         num_files = strtol(optarg, NULL, 0);
276                         break;
277                 case 's':
278                         file_size = strtol(optarg, NULL, 0);
279                         break;
280                 case 'p':
281                         base_dir = optarg;
282                         break;
283                 case 'm':
284                         use_mmap = 1;
285                         break;
286                 case 'S':
287                         use_sync = 1;
288                         break;
289                 case 'l':
290                         loop_count = strtol(optarg, NULL, 0);
291                         break;
292                 case 'h':
293                         usage();
294                         exit(0);
295                 default:
296                         usage();
297                         exit(1);
298                 }
299         }
300
301         argc -= optind;
302         argv += optind;
303
304         /* round up the file size */
305         if (file_size % block_size != 0) {
306                 file_size = (file_size + (block_size-1)) / block_size;
307                 file_size *= block_size;
308                 printf("Rounded file size to %d\n", file_size);
309         }
310
311         printf("num_children=%d file_size=%d num_files=%d loop_count=%d block_size=%d\nmmap=%d sync=%d\n",
312                num_children, file_size, num_files, loop_count, block_size, use_mmap, use_sync);
313
314         printf("Total data size %.1f Mbyte\n",
315                num_files * num_children * 1.0e-6 * file_size);
316
317         /* fork and run run_child() for each child */
318         for (i=0;i<num_children;i++) {
319                 if (fork() == 0) {
320                         run_child(i);
321                         exit(0);
322                 }
323         }
324
325         ret = 0;
326
327         /* wait for children to exit */
328         while (waitpid(0, &status, 0) == 0 || errno != ECHILD) {
329                 if (WEXITSTATUS(status) != 0) {
330                         ret = WEXITSTATUS(status);
331                         printf("Child exited with status %d\n", ret);
332                 }
333         }
334
335         if (ret != 0) {
336                 printf("fstest failed with status %d\n", ret);
337         }
338
339         return ret;
340 }