| /* |
| * Copyright (c) 2000-2001 Silicon Graphics, Inc. |
| * All Rights Reserved. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it would be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #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); |
| } |