| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (c) 2000-2001 Silicon Graphics, Inc. | |
| * All Rights Reserved. | |
| */ | |
| #include "global.h" | |
| #include <ctype.h> | |
| /* | |
| * 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 <number-of-names> 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 <table_data>) into name fields | |
| * of info structures in <table>. | |
| */ | |
| 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); | |
| } |