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