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