fsx: Add fallocate collapse range operation
[xfstests-dev.git] / src / nametest.c
1 /*
2  * Copyright (c) 2000-2001 Silicon Graphics, Inc.
3  * All Rights Reserved.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it would 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 the Free Software Foundation,
16  * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18
19 #include "global.h"
20 #include <ctype.h>
21
22 /*
23  * nametest.c
24  *
25  * Run a fully automatic, random test of the directory routines.
26  *
27  * Given an input file of a list of filenames (one per line)
28  * It does a number of iterations of operations
29  * chosen pseudo-randomly in certain percentages:
30  *   creating (open),
31  *   deleting (unlink) and
32  *   looking up (stat)
33  * on a pseudo-randomly chosen filename (from input file).
34  *
35  * The percentage thresholds for operation selection change
36  * every <number-of-names> iterations.
37  * e.g.
38  * If had 100 names then:
39  * iterations:
40  * 1-100:      pct_remove = 33; pct_create = 33;
41  * 101-200:    pct_remove = 60; pct_create = 20;
42  * 201-300:    pct_remove = 20; pct_create = 60;
43  * 301-400:    pct_remove = 33; pct_create = 33;
44  * 401-500:    pct_remove = 60; pct_create = 20;
45  * 501-600:    pct_remove = 20; pct_create = 60;
46  * etc...
47  *
48  * op > (pct_remove + pct_create) => auto_lookup(ip);
49  * op > pct_remove                => auto_create(ip);
50  * t                              => auto_remove(ip);
51  *
52  * Each iteration an op is chosen as shown above
53  * and a filename is randomly chosen.
54  *
55  * The operation is done and any error codes are
56  * verified considering whether file exists (info.exists)
57  * or not. The stat(3) call also compares inode number.
58  */
59
60
61 #define DOT_COUNT       100     /* print a '.' every X operations */
62
63 struct info {
64         ino64_t inumber;
65         char    *name;
66         short   namelen;
67         short   exists;
68 } *table;
69
70 char *table_data;       /* char string storage for info table */
71
72 int good_adds, good_rms, good_looks, good_tot;  /* ops that suceeded */
73 int bad_adds, bad_rms, bad_looks, bad_tot;      /* ops that failed */
74
75 int verbose;
76 int mixcase;
77
78 int     auto_lookup(struct info *);
79 int     auto_create(struct info *);
80 int     auto_remove(struct info *);
81
82 void    usage(void);
83
84 void
85 usage(void)
86 {
87         printf("usage: nametest [-l srcfile] [-i iterations] [-s seed] [-z] [-v] [-c]\n");
88         exit(1);
89 }
90
91 int
92 main(int argc, char *argv[])
93 {
94         char *sourcefile, *c;
95         int totalnames, iterations, zeroout;
96         int zone, op, pct_remove=0, pct_create=0, ch, i, retval, fd;
97         struct stat64 statb;
98         struct info *ip;
99         int seed, linedots;
100
101         linedots = zeroout = verbose = mixcase = 0;
102         seed = (int)time(NULL) % 1000;
103         iterations = 100000;
104         sourcefile = "input";
105         while ((ch = getopt(argc, argv, "l:i:s:zvc")) != EOF) {
106                 switch (ch) {
107                 case 'l':       sourcefile = optarg;            break;
108                 case 's':       seed = atoi(optarg);            break;
109                 case 'i':       iterations = atoi(optarg);      break;
110                 case 'z':       zeroout++;                      break;
111                 case 'v':       verbose++;                      break;
112                 case 'c':       mixcase++;                      break;
113                 default:        usage();                        break;
114                 }
115         }
116
117         /*
118          * Read in the source file.
119          */
120         if (stat64(sourcefile, &statb) < 0) {
121                 perror(sourcefile);
122                 usage();
123                 return 1;
124         }
125         if ((table_data = malloc(statb.st_size)) == NULL) {
126                 perror("calloc");
127                 return 1;
128         }
129         if ((fd = open(sourcefile, O_RDONLY)) < 0) {
130                 perror(sourcefile);
131                 return 1;
132         }
133         if (read(fd, table_data, statb.st_size) < 0) {
134                 perror(sourcefile);
135                 return 1;
136         }
137         close(fd);
138
139         /*
140          * Allocate space for the info table and fill it in.
141          */
142
143         /*
144          * Add up number of lines in file
145          * and replace '\n' by '\0'
146          */
147         totalnames = 0;
148         for (c = table_data, i = 0; i < statb.st_size; c++, i++) {
149                 if (*c == '\n') {
150                         *c = 0;
151                         totalnames++;
152                 }
153         }
154         if (!totalnames) {
155                 printf("no names found in input file\n");
156                 return 1;
157         }
158
159         table = (struct info *)calloc(totalnames+1, sizeof(struct info));
160         if (table == NULL) {
161                 perror("calloc");
162                 return 1;
163         }
164         /*
165          * Copy over names from file (in <table_data>) into name fields
166          * of info structures in <table>.
167          */
168         ip = table;
169         ip->name = c = table_data;
170         for (i = 0; i < totalnames;  ) {
171                 if (*c++ == 0) {
172                         ip++;
173                         ip->name = c;
174                         i++;
175                 } else {
176                         ip->namelen++;
177                 }
178         }
179         /*
180          * Check table of names.
181          * Name are of files and not commands.
182          *
183          * ??? I guess use of an input file with commands
184          *     has been done before ???
185          * "touch fred" => "fred"
186          * "rm fred" => error
187          * "ls fred" => error
188          */
189         for (ip = table, i = 0; i < totalnames; ip++, i++) {
190                 if (strncmp(ip->name, "touch ", strlen("touch ")) == 0) {
191                         /* make name skip over "touch " string */
192                         ip->name += strlen("touch ");
193                         ip->namelen -= strlen("touch ");
194                 } else if (strncmp(ip->name, "rm ", strlen("rm ")) == 0) {
195                         printf("bad input file, \"rm\" cmds not allowed\n");
196                         return 1;
197                 } else if (strncmp(ip->name, "ls ", strlen("ls ")) == 0) {
198                         printf("bad input file, \"ls\" cmds not allowed\n");
199                         return 1;
200                 }
201         }
202
203         /*
204          * Run random transactions against the directory.
205          */
206         zone = -1;
207         printf("Seed = %d (use \"-s %d\" to re-execute this test)\n", seed, seed);
208         srandom(seed);
209
210         for (i = 0; i < iterations; i++) {
211                 /*
212                  * The distribution of transaction types changes over time.
213                  * At first we have an equal distribution which gives us
214                  * a steady state directory of 50% total size.
215                  * Later, we have an unequal distribution which gives us
216                  * more creates than removes, growing the directory.
217                  * Later still, we have an unequal distribution which gives
218                  * us more removes than creates, shrinking the directory.
219                  */
220                 if ((i % totalnames) == 0) {
221                         zone++;
222                         switch(zone % 3) {
223                         case 0: pct_remove = 20; pct_create = 60; break;
224                         case 1: pct_remove = 33; pct_create = 33; break;
225                         case 2: pct_remove = 60; pct_create = 20; break;
226                         }
227                 }
228
229                 /*
230                  * Choose an operation based on the current distribution.
231                  */
232                 ip = &table[ random() % totalnames ];
233                 op = random() % 100;
234                 if (op > (pct_remove + pct_create)) {
235                         retval = auto_lookup(ip);
236                 } else if (op > pct_remove) {
237                         retval = auto_create(ip);
238                 } else {
239                         retval = auto_remove(ip);
240                 }
241
242                 /* output '.' every DOT_COUNT ops
243                  * and output '\n" every 72 dots
244                  */
245                 if ((i % DOT_COUNT) == 0) {
246                         if (linedots++ == 72) {
247                                 linedots = 0;
248                                 write(1, "\n", 1);
249                         }
250                         write(1, ".", 1);
251                         fflush(stdout);
252                 }
253         }
254         printf("\n");
255
256         printf("creates: %6d OK, %6d EEXIST  (%6d total, %2d%% EEXIST)\n",
257                          good_adds, bad_adds, good_adds + bad_adds,
258                          (good_adds+bad_adds)
259                                  ? (bad_adds*100) / (good_adds+bad_adds)
260                                  : 0);
261         printf("removes: %6d OK, %6d ENOENT  (%6d total, %2d%% ENOENT)\n",
262                          good_rms, bad_rms, good_rms + bad_rms,
263                          (good_rms+bad_rms)
264                                  ? (bad_rms*100) / (good_rms+bad_rms)
265                                  : 0);
266         printf("lookups: %6d OK, %6d ENOENT  (%6d total, %2d%% ENOENT)\n",
267                          good_looks, bad_looks, good_looks + bad_looks,
268                          (good_looks+bad_looks)
269                                  ? (bad_looks*100) / (good_looks+bad_looks)
270                                  : 0);
271         good_tot = good_looks + good_adds + good_rms;
272         bad_tot = bad_looks + bad_adds + bad_rms;
273         printf("total  : %6d OK, %6d w/error (%6d total, %2d%% w/error)\n",
274                          good_tot, bad_tot, good_tot + bad_tot,
275                          (good_tot + bad_tot)
276                                  ? (bad_tot*100) / (good_tot+bad_tot)
277                                  : 0);
278
279         /*
280          * If asked to clear the directory out after the run,
281          * remove everything that is left.
282          */
283         if (zeroout) {
284                 good_rms = 0;
285                 for (ip = table, i = 0; i < totalnames; ip++, i++) {
286                         if (!ip->exists)
287                                 continue;
288                         good_rms++;
289                         retval = unlink(ip->name);
290                         if (retval < 0) {
291                                 if (errno == ENOENT) {
292                                         printf("\"%s\"(%llu) not removed, should have existed\n", ip->name, (unsigned long long)ip->inumber);
293                                 } else {
294                                         printf("\"%s\"(%llu) on remove: ", ip->name, (unsigned long long)ip->inumber);
295                                         perror("unlink");
296                                 }
297                         }
298
299                         if ((good_rms % DOT_COUNT) == 0) {
300                                 write(1, ".", 1);
301                                 fflush(stdout);
302                         }
303                 }
304                 printf("\ncleanup: %6d removes\n", good_rms);
305         }
306         return 0;
307 }
308
309 char *get_name(struct info *ip)
310 {
311         static char path[PATH_MAX];
312         char *p;
313
314         if (!mixcase)
315                 return ip->name;
316
317         /* pick a random character to change case in path */
318         strcpy(path, ip->name);
319         p = strrchr(path, '/');
320         if (!p)
321                 p = path;
322         p += random() % strlen(p);
323         if (islower(*p))
324                 *p = toupper(*p);
325         else
326                 *p = tolower(*p);
327         return path;
328 }
329
330 int
331 auto_lookup(struct info *ip)
332 {
333         struct stat64 statb;
334         int retval;
335
336         retval = stat64(get_name(ip), &statb);
337         if (retval >= 0) {
338                 good_looks++;
339                 retval = 0;
340                 if (ip->exists == 0) {
341                         printf("\"%s\"(%llu) lookup, should not exist\n",
342                                 ip->name, (unsigned long long)statb.st_ino);
343                         retval = 1;
344                 } else if (ip->inumber != statb.st_ino) {
345                         printf("\"%s\"(%llu) lookup, should be inumber %llu\n",
346                                 ip->name, (unsigned long long)statb.st_ino,
347                                 (unsigned long long)ip->inumber);
348                         retval = 1;
349                 } else if (verbose) {
350                         printf("\"%s\"(%llu) lookup ok\n",
351                                 ip->name, (unsigned long long)statb.st_ino);
352                 }
353         } else if (errno == ENOENT) {
354                 bad_looks++;
355                 retval = 0;
356                 if (ip->exists == 1) {
357                         printf("\"%s\"(%llu) lookup, should exist\n",
358                                 ip->name, (unsigned long long)ip->inumber);
359                         retval = 1;
360                 } else if (verbose) {
361                         printf("\"%s\"(%llu) lookup ENOENT ok\n",
362                                 ip->name, (unsigned long long)ip->inumber);
363                 }
364         } else {
365                 retval = errno;
366                 printf("\"%s\"(%llu) on lookup: ",
367                         ip->name, (unsigned long long)ip->inumber);
368                 perror("stat64");
369         }
370         return(retval);
371 }
372
373 int
374 auto_create(struct info *ip)
375 {
376         struct stat64 statb;
377         int retval;
378
379         retval = open(get_name(ip), O_RDWR|O_EXCL|O_CREAT, 0666);
380         if (retval >= 0) {
381                 close(retval);
382                 good_adds++;
383                 retval = 0;
384                 if (stat64(ip->name, &statb) < 0) {
385                         perror("stat64");
386                         exit(1);
387                 }
388                 if (ip->exists == 1) {
389                         printf("\"%s\"(%llu) created, but already existed as inumber %llu\n", ip->name, (unsigned long long)statb.st_ino, (unsigned long long)ip->inumber);
390                         retval = 1;
391                 } else if (verbose) {
392                         printf("\"%s\"(%llu) create new ok\n",
393                                 ip->name, (unsigned long long)statb.st_ino);
394                 }
395                 ip->exists = 1;
396                 ip->inumber = statb.st_ino;
397         } else if (errno == EEXIST) {
398                 bad_adds++;
399                 retval = 0;
400                 if (ip->exists == 0) {
401                         if (stat64(ip->name, &statb) < 0) {
402                                 perror("stat64");
403                                 exit(1);
404                         }
405                         printf("\"%s\"(%llu) not created, should not exist\n",
406                                 ip->name, (unsigned long long)statb.st_ino);
407                         retval = 1;
408                 } else if (verbose) {
409                         printf("\"%s\"(%llu) not created ok\n",
410                                 ip->name, (unsigned long long)ip->inumber);
411                 }
412                 ip->exists = 1;
413         } else {
414                 retval = errno;
415                 printf("\"%s\"(%llu) on create: ",
416                         ip->name, (unsigned long long)ip->inumber);
417                 perror("creat");
418         }
419         return(retval);
420 }
421
422 int
423 auto_remove(struct info *ip)
424 {
425         int retval;
426
427         retval = unlink(get_name(ip));
428         if (retval >= 0) {
429                 good_rms++;
430                 retval = 0;
431                 if (ip->exists == 0) {
432                         printf("\"%s\"(%llu) removed, should not have existed\n",
433                                 ip->name, (unsigned long long)ip->inumber);
434                         retval = 1;
435                 } else if (verbose) {
436                         printf("\"%s\"(%llu) remove ok\n",
437                                 ip->name, (unsigned long long)ip->inumber);
438                 }
439                 ip->exists = 0;
440                 ip->inumber = 0;
441         } else if (errno == ENOENT) {
442                 bad_rms++;
443                 retval = 0;
444                 if (ip->exists == 1) {
445                         printf("\"%s\"(%llu) not removed, should have existed\n",
446                                 ip->name, (unsigned long long)ip->inumber);
447                         retval = 1;
448                 } else if (verbose) {
449                         printf("\"%s\"(%llu) not removed ok\n",
450                                 ip->name, (unsigned long long)ip->inumber);
451                 }
452                 ip->exists = 0;
453         } else {
454                 retval = errno;
455                 printf("\"%s\"(%llu) on remove: ",
456                         ip->name, (unsigned long long)ip->inumber);
457                 perror("unlink");
458         }
459         return(retval);
460 }