// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2000-2001 Silicon Graphics, Inc. * All Rights Reserved. */ #include "global.h" #include /* * nametest.c * * Run a fully automatic, random test of the directory routines. * * Given an input file of a list of filenames (one per line) * It does a number of iterations of operations * chosen pseudo-randomly in certain percentages: * creating (open), * deleting (unlink) and * looking up (stat) * on a pseudo-randomly chosen filename (from input file). * * The percentage thresholds for operation selection change * every iterations. * e.g. * If had 100 names then: * iterations: * 1-100: pct_remove = 33; pct_create = 33; * 101-200: pct_remove = 60; pct_create = 20; * 201-300: pct_remove = 20; pct_create = 60; * 301-400: pct_remove = 33; pct_create = 33; * 401-500: pct_remove = 60; pct_create = 20; * 501-600: pct_remove = 20; pct_create = 60; * etc... * * op > (pct_remove + pct_create) => auto_lookup(ip); * op > pct_remove => auto_create(ip); * t => auto_remove(ip); * * Each iteration an op is chosen as shown above * and a filename is randomly chosen. * * The operation is done and any error codes are * verified considering whether file exists (info.exists) * or not. The stat(3) call also compares inode number. */ #define DOT_COUNT 100 /* print a '.' every X operations */ struct info { ino64_t inumber; char *name; short namelen; short exists; } *table; char *table_data; /* char string storage for info table */ int good_adds, good_rms, good_looks, good_tot; /* ops that suceeded */ int bad_adds, bad_rms, bad_looks, bad_tot; /* ops that failed */ int verbose; int mixcase; int auto_lookup(struct info *); int auto_create(struct info *); int auto_remove(struct info *); void usage(void); void usage(void) { printf("usage: nametest [-l srcfile] [-i iterations] [-s seed] [-z] [-v] [-c]\n"); exit(1); } int main(int argc, char *argv[]) { char *sourcefile, *c; int totalnames, iterations, zeroout; int zone, op, pct_remove=0, pct_create=0, ch, i, retval, fd; struct stat64 statb; struct info *ip; int seed, linedots; linedots = zeroout = verbose = mixcase = 0; seed = (int)time(NULL) % 1000; iterations = 100000; sourcefile = "input"; while ((ch = getopt(argc, argv, "l:i:s:zvc")) != EOF) { switch (ch) { case 'l': sourcefile = optarg; break; case 's': seed = atoi(optarg); break; case 'i': iterations = atoi(optarg); break; case 'z': zeroout++; break; case 'v': verbose++; break; case 'c': mixcase++; break; default: usage(); break; } } /* * Read in the source file. */ if (stat64(sourcefile, &statb) < 0) { perror(sourcefile); usage(); return 1; } if ((table_data = malloc(statb.st_size)) == NULL) { perror("calloc"); return 1; } if ((fd = open(sourcefile, O_RDONLY)) < 0) { perror(sourcefile); return 1; } if (read(fd, table_data, statb.st_size) < 0) { perror(sourcefile); return 1; } close(fd); /* * Allocate space for the info table and fill it in. */ /* * Add up number of lines in file * and replace '\n' by '\0' */ totalnames = 0; for (c = table_data, i = 0; i < statb.st_size; c++, i++) { if (*c == '\n') { *c = 0; totalnames++; } } if (!totalnames) { printf("no names found in input file\n"); return 1; } table = (struct info *)calloc(totalnames+1, sizeof(struct info)); if (table == NULL) { perror("calloc"); return 1; } /* * Copy over names from file (in ) into name fields * of info structures in . */ ip = table; ip->name = c = table_data; for (i = 0; i < totalnames; ) { if (*c++ == 0) { ip++; ip->name = c; i++; } else { ip->namelen++; } } /* * Check table of names. * Name are of files and not commands. * * ??? I guess use of an input file with commands * has been done before ??? * "touch fred" => "fred" * "rm fred" => error * "ls fred" => error */ for (ip = table, i = 0; i < totalnames; ip++, i++) { if (strncmp(ip->name, "touch ", strlen("touch ")) == 0) { /* make name skip over "touch " string */ ip->name += strlen("touch "); ip->namelen -= strlen("touch "); } else if (strncmp(ip->name, "rm ", strlen("rm ")) == 0) { printf("bad input file, \"rm\" cmds not allowed\n"); return 1; } else if (strncmp(ip->name, "ls ", strlen("ls ")) == 0) { printf("bad input file, \"ls\" cmds not allowed\n"); return 1; } } /* * Run random transactions against the directory. */ zone = -1; printf("Seed = %d (use \"-s %d\" to re-execute this test)\n", seed, seed); srandom(seed); for (i = 0; i < iterations; i++) { /* * The distribution of transaction types changes over time. * At first we have an equal distribution which gives us * a steady state directory of 50% total size. * Later, we have an unequal distribution which gives us * more creates than removes, growing the directory. * Later still, we have an unequal distribution which gives * us more removes than creates, shrinking the directory. */ if ((i % totalnames) == 0) { zone++; switch(zone % 3) { case 0: pct_remove = 20; pct_create = 60; break; case 1: pct_remove = 33; pct_create = 33; break; case 2: pct_remove = 60; pct_create = 20; break; } } /* * Choose an operation based on the current distribution. */ ip = &table[ random() % totalnames ]; op = random() % 100; if (op > (pct_remove + pct_create)) { retval = auto_lookup(ip); } else if (op > pct_remove) { retval = auto_create(ip); } else { retval = auto_remove(ip); } /* output '.' every DOT_COUNT ops * and output '\n" every 72 dots */ if ((i % DOT_COUNT) == 0) { if (linedots++ == 72) { linedots = 0; write(1, "\n", 1); } write(1, ".", 1); fflush(stdout); } } printf("\n"); printf("creates: %6d OK, %6d EEXIST (%6d total, %2d%% EEXIST)\n", good_adds, bad_adds, good_adds + bad_adds, (good_adds+bad_adds) ? (bad_adds*100) / (good_adds+bad_adds) : 0); printf("removes: %6d OK, %6d ENOENT (%6d total, %2d%% ENOENT)\n", good_rms, bad_rms, good_rms + bad_rms, (good_rms+bad_rms) ? (bad_rms*100) / (good_rms+bad_rms) : 0); printf("lookups: %6d OK, %6d ENOENT (%6d total, %2d%% ENOENT)\n", good_looks, bad_looks, good_looks + bad_looks, (good_looks+bad_looks) ? (bad_looks*100) / (good_looks+bad_looks) : 0); good_tot = good_looks + good_adds + good_rms; bad_tot = bad_looks + bad_adds + bad_rms; printf("total : %6d OK, %6d w/error (%6d total, %2d%% w/error)\n", good_tot, bad_tot, good_tot + bad_tot, (good_tot + bad_tot) ? (bad_tot*100) / (good_tot+bad_tot) : 0); /* * If asked to clear the directory out after the run, * remove everything that is left. */ if (zeroout) { good_rms = 0; for (ip = table, i = 0; i < totalnames; ip++, i++) { if (!ip->exists) continue; good_rms++; retval = unlink(ip->name); if (retval < 0) { if (errno == ENOENT) { printf("\"%s\"(%llu) not removed, should have existed\n", ip->name, (unsigned long long)ip->inumber); } else { printf("\"%s\"(%llu) on remove: ", ip->name, (unsigned long long)ip->inumber); perror("unlink"); } } if ((good_rms % DOT_COUNT) == 0) { write(1, ".", 1); fflush(stdout); } } printf("\ncleanup: %6d removes\n", good_rms); } return 0; } char *get_name(struct info *ip) { static char path[PATH_MAX]; char *p; if (!mixcase) return ip->name; /* pick a random character to change case in path */ strcpy(path, ip->name); p = strrchr(path, '/'); if (!p) p = path; p += random() % strlen(p); if (islower(*p)) *p = toupper(*p); else *p = tolower(*p); return path; } int auto_lookup(struct info *ip) { struct stat64 statb; int retval; retval = stat64(get_name(ip), &statb); if (retval >= 0) { good_looks++; retval = 0; if (ip->exists == 0) { printf("\"%s\"(%llu) lookup, should not exist\n", ip->name, (unsigned long long)statb.st_ino); retval = 1; } else if (ip->inumber != statb.st_ino) { printf("\"%s\"(%llu) lookup, should be inumber %llu\n", ip->name, (unsigned long long)statb.st_ino, (unsigned long long)ip->inumber); retval = 1; } else if (verbose) { printf("\"%s\"(%llu) lookup ok\n", ip->name, (unsigned long long)statb.st_ino); } } else if (errno == ENOENT) { bad_looks++; retval = 0; if (ip->exists == 1) { printf("\"%s\"(%llu) lookup, should exist\n", ip->name, (unsigned long long)ip->inumber); retval = 1; } else if (verbose) { printf("\"%s\"(%llu) lookup ENOENT ok\n", ip->name, (unsigned long long)ip->inumber); } } else { retval = errno; printf("\"%s\"(%llu) on lookup: ", ip->name, (unsigned long long)ip->inumber); perror("stat64"); } return(retval); } int auto_create(struct info *ip) { struct stat64 statb; int retval; retval = open(get_name(ip), O_RDWR|O_EXCL|O_CREAT, 0666); if (retval >= 0) { close(retval); good_adds++; retval = 0; if (stat64(ip->name, &statb) < 0) { perror("stat64"); exit(1); } if (ip->exists == 1) { printf("\"%s\"(%llu) created, but already existed as inumber %llu\n", ip->name, (unsigned long long)statb.st_ino, (unsigned long long)ip->inumber); retval = 1; } else if (verbose) { printf("\"%s\"(%llu) create new ok\n", ip->name, (unsigned long long)statb.st_ino); } ip->exists = 1; ip->inumber = statb.st_ino; } else if (errno == EEXIST) { bad_adds++; retval = 0; if (ip->exists == 0) { if (stat64(ip->name, &statb) < 0) { perror("stat64"); exit(1); } printf("\"%s\"(%llu) not created, should not exist\n", ip->name, (unsigned long long)statb.st_ino); retval = 1; } else if (verbose) { printf("\"%s\"(%llu) not created ok\n", ip->name, (unsigned long long)ip->inumber); } ip->exists = 1; } else { retval = errno; printf("\"%s\"(%llu) on create: ", ip->name, (unsigned long long)ip->inumber); perror("creat"); } return(retval); } int auto_remove(struct info *ip) { int retval; retval = unlink(get_name(ip)); if (retval >= 0) { good_rms++; retval = 0; if (ip->exists == 0) { printf("\"%s\"(%llu) removed, should not have existed\n", ip->name, (unsigned long long)ip->inumber); retval = 1; } else if (verbose) { printf("\"%s\"(%llu) remove ok\n", ip->name, (unsigned long long)ip->inumber); } ip->exists = 0; ip->inumber = 0; } else if (errno == ENOENT) { bad_rms++; retval = 0; if (ip->exists == 1) { printf("\"%s\"(%llu) not removed, should have existed\n", ip->name, (unsigned long long)ip->inumber); retval = 1; } else if (verbose) { printf("\"%s\"(%llu) not removed ok\n", ip->name, (unsigned long long)ip->inumber); } ip->exists = 0; } else { retval = errno; printf("\"%s\"(%llu) on remove: ", ip->name, (unsigned long long)ip->inumber); perror("unlink"); } return(retval); }