]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/nhfsstone/nhfsstone.c
Initial revision
[nfs-utils.git] / utils / nhfsstone / nhfsstone.c
1 #ifndef lint
2 static char sccsid[] = "@(#)nhfsstone.c 1.22 90/05/08 Copyright (c) 1990, Legato Systems Inc";
3 #endif
4
5 /*
6  * Copyright (c) 1990 Legato Systems Inc.
7  *
8  * See DISCLAIMER file for restrictions
9  *
10  * Ported to Linux by Olaf Kirch <okir@monad.swb.de>
11  */
12
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <unistd.h>
16 #include <string.h>
17 #include <ctype.h>
18 #include <errno.h>
19 #include <fcntl.h>
20 #include <sys/param.h>
21 #include <sys/time.h>
22 #include <sys/vfs.h>
23 #include <sys/stat.h>
24 #include <sys/wait.h>
25 #ifdef BSD
26 #include <sys/dir.h>
27 #define dirent  direct
28 #else
29 #include <dirent.h>
30 #endif
31 #include <signal.h>
32
33 #ifndef NULL
34 #define NULL    0
35 #endif
36
37 /*
38  * Usage: nhfsstone [-v] [[-t secs] | [-c calls]] [-l load] [-p nprocs]
39  *                                              [-m mixfile] [dir]...
40  *
41  * Generates an artifical NFS client load based on a given mix of
42  * operations.
43  *
44  * Strategy: loop for some number of NFS calls doing a random sleep
45  * followed by a call to one of the op generator routines. The routines
46  * are called based on a weighting factor determined by the difference
47  * between the current ops percentages (derived from kernel NFS stats)
48  * and a set of default percentages or a mix supplied by the caller.
49  *
50  * The generator routines try very hard to guess how many NFS operations
51  * they are generating so that the calling routine can keep a running
52  * estimate of the number of calls and the mix to avoid having to get
53  * the NFS statistics from the kernel too often.
54  *
55  * The operations are done in a directory that has a set of file names
56  * that are long enough that they won't be cached by the name cache
57  * in the kernel. The "lookup" operation steps through the names and
58  * creates a file if that name does not exist, or closes and reopens it
59  * if it does. This generates a table of open file descriptors. Most of the
60  * other operations are done on random descriptors in the table. The "getattr"
61  * operation tries to avoid the kernel attribute cache by doing "fstat"
62  * system calls on random descriptors in the table. There must be enough
63  * files in the directory so that, on average, the getattr operation hits
64  * any file less often than once each 6 seconds (the default timeout for
65  * the attributes cache).
66  *
67  * The parent process starts children to do the real work of generating load.
68  * The parent coordinates them so that they all start at the same time, and
69  * collects statistics from them when they are done. To coordinate the
70  * start up, the parent waits for each child to write one byte into
71  * a common log file (opened in append mode to avoid overwriting).
72  * After they write a byte the children pause, and the parent send SIGUSR1
73  * when it has heard from all of the kids. The children write their statistics
74  * into the same common log file and the parent reads and accumulates the
75  * statics and prints them out.
76  *
77  * This code will only compile and run on 4.X BSD based systems.
78  */
79
80 #define DEFAULT_LOAD    30              /* default calls per sec */
81 #define DEFAULT_CALLS   5000            /* default number of calls */
82 #define NFILES          40              /* number of test files/dir */
83 #define BUFSIZE         8192            /* block size for read and write */
84 #define MAXFILESIZE     32              /* size, in blocks, of large file */
85 #define SAMPLETIME      5               /* secs between samples of NFS stats */
86 #define NPROCS          7               /* number of children to run */
87
88
89 /*
90  * The names of NFS operations
91  */
92 char *Opnames[] = {
93         "null", "getattr", "setattr", "root", "lookup", "readlink", "read",
94         "wrcache", "write", "create", "remove", "rename", "link", "symlink",
95         "mkdir", "rmdir", "readdir", "fsstat",
96 };
97
98 /*
99  * NFS operation numbers
100  *
101  * Used to index the Opnames, Mix and statistics arrays.
102  */
103 #define NOPS            18              /* number of NFS ops */
104 #define NULLCALL        0
105 #define GETATTR         1
106 #define SETATTR         2
107 #define ROOT            3
108 #define LOOKUP          4
109 #define READLINK        5
110 #define READ            6
111 #define WRCACHE         7
112 #define WRITE           8
113 #define CREATE          9
114 #define REMOVE          10
115 #define RENAME          11
116 #define LINK            12
117 #define SYMLINK         13
118 #define MKDIR           14
119 #define RMDIR           15
120 #define READDIR         16
121 #define FSSTAT          17
122
123 /*
124  * Operations counts
125  */
126 struct count {
127         int             total;
128         int             calls[NOPS];
129 };
130
131 /*
132  * Software development mix for server with about 50/50 mix of
133  * diskless and diskful clients running SunOS 4.0.
134  */
135 int     Mix[NOPS] = {
136         0,              /* null */
137         13,             /* getattr */
138         1,              /* setattr */
139         0,              /* root */
140         34,             /* lookup */
141         8,              /* readlink */
142         22,             /* read */
143         0,              /* wrcache */
144         15,             /* write */
145         2,              /* create */
146         1,              /* remove */
147         0,              /* rename */
148         0,              /* link */
149         0,              /* symlink */
150         0,              /* mkdir */
151         0,              /* rmdir */
152         3,              /* readdir */
153         1,              /* fsstat */
154 };
155
156 /* Prototype decls */
157 int     setmix(FILE *fp);
158 void    usage(void);
159 void    init_logfile(void);
160 void    init_counters(void);
161 void    get_delta(struct count *start, struct count *cur);
162 void    init_testdir(int dirnum, char *parentdir);
163 void    do_op(int rpct);
164 void    op(int opnum);
165 void    nextfile(void);
166 int     createfile(void);
167 int     openfile(void);
168 int     writefile(void);
169 void    collect_counters(void);
170 int     check_counters(void);
171 void    print(void);
172 void    msec_sleep(int msecs);
173 void    get_opct(struct count *count);
174 int     substr(char *sp, char *subsp);
175 int     check_access(struct stat statb);
176 void    error(char *str);
177
178 /*
179  * NFS operations generator routines
180  */
181 int     op_null();
182 int     op_getattr();
183 int     op_setattr();
184 int     op_root();
185 int     op_lookup();
186 int     op_readlink();
187 int     op_read();
188 int     op_wrcache();
189 int     op_write();
190 int     op_create();
191 int     op_remove();
192 int     op_rename();
193 int     op_link();
194 int     op_symlink();
195 int     op_mkdir();
196 int     op_rmdir();
197 int     op_readdir();
198 int     op_fsstat();
199
200 /*
201  * Operations generator vector
202  */
203 struct op_vect {
204         int     (*funct)();     /* op */
205 } Op_vect[NOPS] = {
206         { op_null },
207         { op_getattr },
208         { op_setattr },
209         { op_root },
210         { op_lookup },
211         { op_readlink },
212         { op_read },
213         { op_wrcache },
214         { op_write },
215         { op_create },
216         { op_remove },
217         { op_rename },
218         { op_link },
219         { op_symlink },
220         { op_mkdir },
221         { op_rmdir },
222         { op_readdir },
223         { op_fsstat },
224 };
225
226 /*
227  * Name sub-strings
228  */
229 #define DIRSTR  "dir"                   /* directory */
230 #define SYMSTR  "sym"                   /* symbolic link */
231 #define LINSTR  "lin"                   /* hard link */
232
233 struct timeval  Optime[NOPS+1];         /* cumulative running time for ops */
234 struct count Curct;                     /* total number ops called */
235 int     Openfd[NFILES];                 /* open file descriptors */
236 int     Curnum;                         /* current file number */
237 int     Symnum;                         /* current symlink file number */
238 int     Linknum;                        /* current link file number */
239 int     Dirnum;                         /* current directory number */
240 DIR     *Testdir;                       /* my test directory */
241 char    Testdirname[MAXNAMLEN*2];       /* my test directory name */
242 char    Curname[MAXNAMLEN];             /* current file name */
243 char    Dirname[MAXNAMLEN];             /* current directory name */
244 char    Symname[MAXNAMLEN];             /* symlink file name */
245 char    Linkname[MAXNAMLEN];            /* link file name */
246 char    *Otherspec = "%s/%03d";         /* sprintf spec for other names */
247 char    *Rename1 = "rename1";           /* first name of rename pair */
248 char    *Rename2 = "rename2";           /* second name of rename pair */
249 char    *Symlinkpath = "./symlinknamelongstuff";
250                                         /* symlink file data */
251 char    *Myname;                        /* name program invoked under */
252 char    Namebuf[MAXNAMLEN];             /* unique name for this program */
253 int     Log;                            /* synchronization log */
254 char    Logname[MAXNAMLEN];             /* synchronization log name */
255 int     Kmem;                           /* /dev/kmem file descriptor */
256 off_t   Statoffset;                     /* offset to op count in NFS stats */
257 int     Nprocs;                         /* sub-processes started */
258 int     Verbose;                        /* print more info */
259 int     Testop = -1;                    /* operation to test */
260 int     Saveerrno;                      /* place to save errno */
261
262 #define subtime(t1, t2) {if ((t1.tv_usec -= t2.tv_usec) >= 1000000) {\
263                                 t1.tv_sec += (t1.tv_usec / 1000000); \
264                                 t1.tv_usec %= 1000000; \
265                          } else if (t1.tv_usec < 0) { \
266                                 t1.tv_usec += 1000000; \
267                                 t1.tv_sec--; \
268                          } \
269                          t1.tv_sec -= t2.tv_sec; \
270                         }
271
272 #define addtime(t1, t2) {if ((t1.tv_usec += t2.tv_usec) >= 1000000) {\
273                                 t1.tv_sec += (t1.tv_usec / 1000000); \
274                                 t1.tv_usec %= 1000000; \
275                          } else if (t1.tv_usec < 0) { \
276                                 t1.tv_usec += 1000000; \
277                                 t1.tv_sec--; \
278                          } \
279                          t1.tv_sec += t2.tv_sec; \
280                         }
281
282 /*
283  * Used to catch the parent's "start" signal
284  */
285 void
286 startup()
287 {
288
289         return;
290 }
291
292 /*
293  * Clean up and exit
294  */
295 void
296 cleanup()
297 {
298
299         (void) unlink(Logname);
300         exit(1);
301 }
302
303 int
304 main(int argc, char **argv)
305 {
306         int runtime;            /* length of run, in seconds */
307         int load;               /* load factor, in client loads */
308         int ncalls;             /* total number of calls to make */
309         int avgmspc;            /* average millisec per call */
310         int mspc;               /* millisec per call */
311         int wantcalls;          /* ncalls that should have happend by now */
312         int pid;                /* process id */
313         int delay;              /* msecs since last checked current time */
314         int randnum;            /* a random number */
315         int oldmask;            /* saved signal mask */
316         int sampletime;         /* secs between reading kernel stats */
317         char *opts;             /* option parsing */
318         int pct;
319         int procnum;
320         FILE *fp;
321         struct timeval curtime;
322         struct timeval starttime;
323         struct count startct;
324         struct stat statb;
325         char workdir[MAXPATHLEN];
326         char *getwd();
327
328         Myname = argv[0];
329
330         argc--;
331         argv++;
332
333         load = DEFAULT_LOAD;
334         ncalls = 0;
335         runtime = 0;
336         Nprocs = NPROCS;
337         pid = 0;
338
339         (void) umask(0);
340
341         /*
342          * Parse options
343          */
344         while (argc && **argv == '-') {
345                 opts = &argv[0][1];
346                 while (*opts) {
347                         switch (*opts) {
348
349                         case 'c':
350                                 /*
351                                  * Set number of calls
352                                  */
353                                 if (!isdigit(argv[1][0])) {
354                                         (void) fprintf(stderr,
355                                             "%s: illegal calls value %s\n",
356                                             Myname, argv[1]);
357                                         exit(1);
358                                 }
359                                 ncalls = atoi(argv[1]);
360                                 argv++;
361                                 argc--;
362                                 break;
363
364                         case 'l':
365                                 /*
366                                  * Set load
367                                  */
368                                 if (!isdigit(argv[1][0])) {
369                                         (void) fprintf(stderr,
370                                             "%s: illegal load value %s\n",
371                                             Myname, argv[1]);
372                                         exit(1);
373                                 }
374                                 load = atoi(argv[1]);
375                                 argv++;
376                                 argc--;
377                                 break;
378
379                         case 'm':
380                                 /*
381                                  * Set mix from a file
382                                  */
383                                 if ((fp = fopen(argv[1], "r")) == NULL) {
384                                         Saveerrno = errno;
385                                         (void) fprintf(stderr,
386                                             "%s: bad mix file", Myname);
387                                         errno = Saveerrno;
388                                         perror("");
389                                         exit(1);
390                                 }
391                                 if (setmix(fp) < 0) {
392                                         exit(1);
393                                 }
394                                 (void) fclose(fp);
395                                 argv++;
396                                 argc--;
397                                 break;
398
399                         case 'p':
400                                 /*
401                                  * Set number of child processes
402                                  */
403                                 if (!isdigit(argv[1][0])) {
404                                         (void) fprintf(stderr,
405                                             "%s: illegal procs value %s\n",
406                                             Myname, argv[1]);
407                                         exit(1);
408                                 }
409                                 Nprocs = atoi(argv[1]);
410                                 argv++;
411                                 argc--;
412                                 break;
413
414                         case 'T':
415                                 /*
416                                  * Set test mode, number following is opnum
417                                  */
418                                 if (!isdigit(argv[1][0])) {
419                                         (void) fprintf(stderr,
420                                             "%s: illegal test value %s\n",
421                                             Myname, argv[1]);
422                                         exit(1);
423                                 }
424                                 Testop = atoi(argv[1]);
425                                 if (Testop >= NOPS) {
426                                         (void) fprintf(stderr,
427                                             "%s: illegal test value %d\n",
428                                             Myname, Testop);
429                                         exit(1);
430                                 }
431                                 argv++;
432                                 argc--;
433                                 break;
434
435                         case 't':
436                                 /*
437                                  * Set running time
438                                  */
439                                 if (!isdigit(argv[1][0])) {
440                                         (void) fprintf(stderr,
441                                             "%s: illegal time value %s\n",
442                                             Myname, argv[1]);
443                                         exit(1);
444                                 }
445                                 runtime = atoi(argv[1]);
446                                 argv++;
447                                 argc--;
448                                 break;
449
450                         case 'v':
451                                 /*
452                                  * Set verbose mode
453                                  */
454                                 Verbose++;
455                                 break;
456
457                         default:
458                                 usage();
459                                 exit(1);
460
461                         }
462                         opts++;
463                 }
464                 argv++;
465                 argc--;
466         }
467
468         init_logfile();         /* Set up synchronizatin log file */
469
470         if (getcwd(workdir, sizeof(workdir)) == (char *) 0) {
471                 Saveerrno = errno;
472                 (void) fprintf(stderr,
473                     "%s: can't find current directory ", Myname);
474                 errno = Saveerrno;
475                 perror("");
476                 exit(1);
477         }
478
479         (void) signal(SIGINT, cleanup);
480         (void) signal(SIGUSR1, startup);
481         oldmask = sigblock(sigmask(SIGUSR1));
482
483         if (ncalls == 0) {
484                 if (runtime == 0) {
485                         ncalls = DEFAULT_CALLS;
486                 } else {
487                         ncalls = runtime * load;
488                 }
489         }
490         avgmspc = Nprocs * 1000 / load;
491
492         /*
493          * Fork kids
494          */
495         for (procnum = 0; procnum < Nprocs; procnum++) {
496                 if ((pid = fork()) == -1) {
497                         Saveerrno = errno;
498                         (void) fprintf(stderr, "%s: can't fork ", Myname);
499                         errno = Saveerrno;
500                         perror("");
501                         (void) kill(0, SIGINT);
502                         exit(1);
503                 }
504                 /*
505                  * Kids go initialize
506                  */
507                 if (pid == 0) {
508                         break;
509                 }
510         }
511
512         /*
513          * Parent: wait for kids to get ready, start them, wait for them to
514          * finish, read and accumulate results.
515          */
516         if (pid != 0) {
517                 /*
518                  * wait for kids to initialize
519                  */
520                 do {
521                         sleep(1);
522                         if (fstat(Log, &statb) == -1) {
523                                 (void) fprintf(stderr, "%s: can't stat log %s",
524                                     Myname, Logname);
525                                 (void) kill(0, SIGINT);
526                                 exit(1);
527                         }
528                 } while (statb.st_size != Nprocs);
529
530                 if (ftruncate(Log, 0L) == -1) {
531                         (void) fprintf(stderr, "%s: can't truncate log %s",
532                             Myname, Logname);
533                         (void) kill(0, SIGINT);
534                         exit(1);
535                 }
536
537                 sync();
538                 sleep(3);
539
540                 /*
541                  * Be sure there isn't something else going on
542                  */
543                 get_opct(&startct);
544                 msec_sleep(2000);
545                 get_delta(&startct, &Curct);
546                 if (Curct.total > 20) {
547                         (void) fprintf(stderr,
548                             "%s: too much background activity (%d calls/sec)\n",
549                             Myname, Curct.total);
550                         (void) kill(0, SIGINT);
551                         exit(1);
552                 }
553
554                 /*
555                  * get starting stats
556                  */
557                 get_opct(&startct);
558
559                 /*
560                  * Start kids
561                  */
562                 (void) kill(0, SIGUSR1);
563
564                 /*
565                  * Kids started, wait for first one to finish, signal the
566                  * rest and wait for them to finish.
567                  */
568                 if (wait((union wait *) 0) != -1) {
569                         (void) kill(0, SIGUSR1);
570                         while (wait((union wait *) 0) != -1)
571                                 /* nothing */;
572                 }
573
574                 /*
575                  * Initialize and sum up counters
576                  */
577                 init_counters();
578                 get_delta(&startct, &Curct);
579                 collect_counters();
580                 if (check_counters() == -1) {
581                         Verbose = 1;
582                 }
583                 print();
584
585                 (void) close(Log);
586                 (void) unlink(Logname);
587
588                 exit(0);
589         }
590
591         /*
592          * Children: initialize, then notify parent through log file,
593          * wait to get signal, beat the snot out of the server, write
594          * stats to the log file, and exit.
595          */
596
597         /*
598          * Change my name for error logging
599          */
600         (void) sprintf(Namebuf, "%s%d", Myname, procnum);
601         Myname = Namebuf;
602
603         /*
604          * Initialize and cd to test directory
605          */
606         if (argc != 0) {
607                 init_testdir(procnum, argv[procnum % argc]);
608         } else {
609                 init_testdir(procnum, ".");
610         }
611         if ((Testdir = opendir(".")) == NULL) {
612                 Saveerrno = errno;
613                 (void) fprintf(stderr,
614                     "%s: can't open test directory ", Myname);
615                 errno = Saveerrno;
616                 perror(Testdirname);
617                 exit(1);
618         }
619
620         init_counters();
621         srandom(procnum+1);
622
623         /*
624          * Tell parent I'm ready then wait for go ahead
625          */
626         if (write(Log, " ", 1) != 1) {
627                 (void) fprintf(stderr, "%s: can't write sync file %s",
628                     Myname, Logname);
629                 (void) kill(0, SIGINT);
630                 exit(1);
631         }
632
633         sigpause(oldmask);
634
635         /*
636          * Initialize counters
637          */
638         get_opct(&startct);
639         (void) gettimeofday(&starttime, (struct timezone *)NULL);
640         sampletime = starttime.tv_sec + ((int) random()) % (2 * SAMPLETIME);
641         curtime = starttime;
642
643         /*
644          * Do pseudo NFS operations and adapt to dynamic changes in load
645          * by adjusting the sleep time between operations based on the
646          * number of calls that should have occured since starttime and
647          * the number that have actually occured.  A delay is used to avoid
648          * doing gettimeofday calls too often, and a sampletime is
649          * used to avoid reading kernel NFS stats too often.
650          * If parent interrupts, get out and clean up.
651          */
652         delay = 0;
653         mspc = avgmspc;
654         for (;;) {
655                 randnum = (int) random();
656                 if (mspc > 0) {
657                         msec_sleep(randnum % (mspc << 1));
658                 }
659
660                 /*
661                  * Do the NFS operation
662                  * We use a random number from 0-199 to avoid starvation
663                  * of the operations at the end of the mix.
664                  */
665                 do_op(randnum % 200);
666
667                 /*
668                  * Do a gettimeofday call only once per second
669                  */
670                 delay += mspc;
671                 if (delay > 1000 || Curct.total >= ncalls) {
672                         delay = 0;
673                         (void) gettimeofday(&curtime, (struct timezone *)NULL);
674
675                         /*
676                          * If sample time is up, check the kernel stats
677                          * and adjust our parameters to either catch up or
678                          * slow down.
679                          */
680                         if (curtime.tv_sec > sampletime ||
681                             Curct.total >= ncalls) {
682                                 sampletime = curtime.tv_sec + SAMPLETIME;
683                                 get_delta(&startct, &Curct);
684                                 if (Curct.total >= ncalls) {
685                                         break;
686                                 }
687                                 wantcalls =
688                                     ((curtime.tv_sec - starttime.tv_sec) * 1000
689                                     +(curtime.tv_usec-starttime.tv_usec) / 1000)
690                                     * Nprocs / avgmspc;
691                                 pct = 1000 * (Curct.total - wantcalls) / ncalls;
692                                 mspc = avgmspc + avgmspc * pct / 20;
693                                 if (mspc <= 0) {
694                                         /*
695                                          * mspc must be positive or we will
696                                          * never advance time.
697                                          */
698                                         mspc = 10;
699                                 }
700                         }
701                 }
702         }
703
704         /*
705          * Store total time in last slot of counts array
706          */
707         Optime[NOPS].tv_sec = curtime.tv_sec - starttime.tv_sec;
708         Optime[NOPS].tv_usec = curtime.tv_usec - starttime.tv_usec;
709
710         /*
711          * write stats to log file (append mode)
712          */
713         if (write(Log, (char *)Optime, sizeof (Optime)) == -1) {
714                 Saveerrno = errno;
715                 (void) fprintf(stderr, "%s: can't write log ", Myname);
716                 errno = Saveerrno;
717                 perror("");
718                 (void) kill(0, SIGINT);
719                 exit(1);
720         }
721         (void) close(Log);
722
723         exit(0);
724 }
725
726 /*
727  * Initialize test directory
728  *
729  * If the directory already exists, check to see that all of the
730  * files exist and we can write them.  If directory doesn't exist
731  * create it and fill it using the LOOKUP and WRITE ops.
732  * Chdir to the directory.
733  */
734 void
735 init_testdir(int dirnum, char *parentdir)
736 {
737         int i;
738         int fd;
739         char cmd[256];
740         struct stat statb;
741
742         (void) sprintf(Testdirname, "%s/testdir%d", parentdir, dirnum);
743         if (stat(Testdirname, &statb) == -1) {
744                 if (mkdir(Testdirname, 0777) == -1) {
745                         Saveerrno = errno;
746                         (void) fprintf(stderr,
747                             "%s: can't create test directory ", Myname);
748                         errno = Saveerrno;
749                         perror(Testdirname);
750                         (void) kill(0, SIGINT);
751                         exit(1);
752                 }
753                 if (chdir(Testdirname) == -1) {
754                         Saveerrno = errno;
755                         (void) fprintf(stderr,
756                             "%s: can't cd to test directory ", Myname);
757                         errno = Saveerrno;
758                         perror(Testdirname);
759                         (void) kill(0, SIGINT);
760                         exit(1);
761                 }
762
763                 /*
764                  * create some files with long names and average size
765                  */
766                 for (i = 0; i < NFILES; i++) {
767                         nextfile();
768                         (void) createfile();
769                         if (Openfd[Curnum] == 0 || writefile() == 0) {
770                                 Saveerrno = errno;
771                                 (void) fprintf(stderr,
772                                     "%s: can't create test file '%s'\n",
773                                     Myname, Curname);
774                                 errno = Saveerrno;
775                                 perror(Testdirname);
776                                 (void) kill(0, SIGINT);
777                                 exit(1);
778                         }
779                 }
780         } else {
781                 if (chdir(Testdirname) == -1) {
782                         Saveerrno = errno;
783                         (void) fprintf(stderr,
784                             "%s: can't cd to test directory ", Myname);
785                         errno = Saveerrno;
786                         perror(Testdirname);
787                         (void) kill(0, SIGINT);
788                         exit(1);
789                 }
790
791                 /*
792                  * Verify that we can read and write the test dir
793                  */
794                 if (check_access(statb) == -1) {
795                         (void) fprintf(stderr,
796                             "%s: wrong permissions on test dir %s\n",
797                             Myname, Testdirname);
798                         (void) kill(0, SIGINT);
799                         exit(1);
800                 }
801
802                 /*
803                  * Verify that we can read and write all the files
804                  */
805                 for (i = 0; i < NFILES; i++) {
806                         nextfile();
807                         if (stat(Curname, &statb) == -1 || statb.st_size == 0) {
808                                 /*
809                                  * File doesn't exist or is 0 size
810                                  */
811                                 (void) createfile();
812                                 if (Openfd[Curnum] == 0 || writefile() == 0) {
813                                         (void) kill(0, SIGINT);
814                                         exit(1);
815                                 }
816                         } else if (check_access(statb) == -1) {
817                                 /*
818                                  * should try to remove and recreate it
819                                  */
820                                 (void) fprintf(stderr,
821                                     "%s: wrong permissions on testfile %s\n",
822                                     Myname, Curname);
823                                 (void) kill(0, SIGINT);
824                                 exit(1);
825                         } else if (Openfd[Curnum] == 0) {
826                                 (void) openfile();
827                                 if (Openfd[Curnum] == 0) {
828                                         (void) kill(0, SIGINT);
829                                         exit(1);
830                                 }
831                         }
832                 }
833         }
834
835         /*
836          * Start with Rename1 and no Rename2 so the
837          * rename op can ping pong back and forth.
838          */
839         (void) unlink(Rename2);
840         if ((fd = open(Rename1, O_CREAT|O_TRUNC|O_RDWR, 0666)) == -1) {
841                 Saveerrno = errno;
842                 (void) fprintf(stderr, "%s: can't create rename file ", Myname);
843                 errno = Saveerrno;
844                 perror(Rename1);
845                 (void) kill(0, SIGINT);
846                 exit(1);
847         }
848
849         /*
850          * Remove and recreate the test sub-directories
851          * for mkdir symlink and hard link.
852          */
853         (void) sprintf(cmd, "rm -rf %s %s %s", DIRSTR, SYMSTR, LINSTR);
854         if (system(cmd) != 0) {
855                 (void) fprintf(stderr, "%s: can't %s\n", Myname, cmd);
856                 (void) kill(0, SIGINT);
857                 exit(1);
858         }
859
860         if (mkdir(DIRSTR, 0777) == -1) {
861                 (void) fprintf(stderr,
862                     "%s: can't create subdir %s\n", Myname, DIRSTR);
863                 (void) kill(0, SIGINT);
864                 exit(1);
865         }
866
867         if (mkdir(SYMSTR, 0777) == -1) {
868                 (void) fprintf(stderr,
869                     "%s: can't create subdir %s\n", Myname, SYMSTR);
870                 (void) kill(0, SIGINT);
871                 exit(1);
872         }
873         op(SYMLINK);
874
875         if (mkdir(LINSTR, 0777) == -1) {
876                 (void) fprintf(stderr, "%s: can't create subdir %s\n", Myname,
877                     LINSTR);
878                 (void) kill(0, SIGINT);
879                 exit(1);
880         }
881
882         (void) close(fd);
883 }
884
885 /*
886  * The routines below attempt to do over-the-wire operations.
887  * Each op tries to cause one or more of a particular
888  * NFS operation to go over the wire.  OPs return the number
889  * of OTW calls they think they have generated.
890  *
891  * An array of open file descriptors is kept for the files in each
892  * test directory. The open fd's are used to get access to the files
893  * without generating lookups. An fd value of 0 mean the corresponding
894  * file name is closed.  Ops that need a name use Curname.
895  */
896
897 /*
898  * Call an op based on a random number and the current
899  * op calling weights. Op weights are derived from the
900  * mix percentage and the current NFS stats mix percentage.
901  */
902 void
903 do_op(int rpct)
904 {
905         int opnum;
906         int weight;
907         int oppct;
908
909         if (Testop != -1) {
910                 nextfile();
911                 op(Testop);
912                 return;
913         }
914         for (opnum = rpct % NOPS; rpct >= 0; opnum = (opnum + 1) % NOPS) {
915                 if (Curct.total) {
916                         oppct = (Curct.calls[opnum] * 100) / Curct.total;
917                 } else {
918                         oppct = 0;
919                 }
920                 /*
921                  * Weight is mix percent - (how far off we are * fudge)
922                  * fudge factor is required because some ops (read, write)
923                  * generate many NFS calls for a single op call
924                  */
925                 weight = Mix[opnum] - ((oppct - Mix[opnum]) << 4);
926                 if (weight <= 0) {
927                         continue;
928                 }
929                 rpct -= weight;
930                 if (rpct < 0) {
931                         if (opnum == RMDIR && Dirnum == 0) {
932                                 op(MKDIR);
933                         } else if (opnum != CREATE && opnum != LOOKUP &&
934                             opnum != REMOVE) {
935                                 nextfile();
936                         }
937                         op(opnum);
938                         if (Openfd[Curnum] == 0) {
939                                 op(CREATE);
940 #ifdef XXX
941                                 op(WRITE);
942 #endif /* XXX */
943                         }
944                         return;
945                 }
946         }
947 }
948
949 /*
950  * Call an op generator and keep track of its running time
951  */
952 void
953 op(int opnum)
954 {
955         struct timeval start;
956         struct timeval stop;
957         int nops;
958
959         (void) gettimeofday(&start, (struct timezone *)NULL);
960         nops = (*Op_vect[opnum].funct)();
961         (void) gettimeofday(&stop, (struct timezone *)NULL);
962         stop.tv_sec -= start.tv_sec;
963         stop.tv_usec -= start.tv_usec;
964
965 #ifdef SUNOS4
966         /*
967          * SunOS 4.0 does a lookup and a getattr on each open
968          * so we have to account for that in the getattr op
969          */
970         if (opnum == GETATTR && nops == 2) {
971                 nops = 1;
972                 stop.tv_sec /= 2;
973                 stop.tv_usec /= 2;
974                 Curct.total += Nprocs;
975                 Curct.calls[LOOKUP] += Nprocs;
976                 addtime(Optime[LOOKUP], stop);
977         }
978 #endif
979
980         nops *= Nprocs;
981         Curct.total += nops;
982         Curct.calls[opnum] += nops;
983         addtime(Optime[opnum], stop);
984 }
985
986 /*
987  * Advance file number (Curnum) and name (Curname)
988  */
989 void
990 nextfile(void)
991 {
992         static char *numpart = NULL;
993         int num;
994
995         Curnum = (Curnum + 1) % NFILES;
996         if (numpart == NULL) {
997                 (void) sprintf(Curname, "%03dabcdefghijklmn", Curnum);
998                 numpart = Curname;
999         } else {
1000                 num = Curnum;
1001                 numpart[0] = '0' + num / 100;
1002                 num %= 100;
1003                 numpart[1] = '0' + num / 10;
1004                 num %= 10;
1005                 numpart[2] = '0' + num;
1006         }
1007 }
1008
1009 int
1010 createfile(void)
1011 {
1012         int ret;
1013         int fd;
1014
1015         ret = 0;
1016         fd = Openfd[Curnum];
1017
1018         if ((fd && close(fd) == -1) ||
1019             (fd = open(Curname, O_CREAT|O_RDWR|O_TRUNC, 0666)) == -1) {
1020                 fd = 0;
1021                 ret = -1;
1022                 error("create");
1023         }
1024         Openfd[Curnum] = fd;
1025         return (ret);
1026 }
1027
1028 int
1029 openfile(void)
1030 {
1031         int ret;
1032         int fd;
1033
1034         ret = 0;
1035         fd = Openfd[Curnum];
1036         if (fd == 0 && (fd = open(Curname, O_RDWR, 0666)) == -1) {
1037                 fd = 0;
1038                 ret = -1;
1039                 error("open");
1040         }
1041         Openfd[Curnum] = fd;
1042         return (ret);
1043 }
1044
1045 int
1046 writefile(void)
1047 {
1048         int fd;
1049         int wrote;
1050         int bufs;
1051         int size;
1052         int randnum;
1053         char buf[BUFSIZE];
1054
1055         fd = Openfd[Curnum];
1056
1057         if (lseek(fd, 0L, 0) == (off_t) -1) {
1058                 error("write: lseek");
1059                 return (-1);
1060         }
1061
1062         randnum = (int) random();
1063         bufs = randnum % 100;   /* using this for distribution desired */
1064         /*
1065          * Attempt to create a distribution of file sizes
1066          * to reflect reality.  Most files are small,
1067          * but there are a few files that are very large.
1068          *
1069          * The sprite paper (USENIX 198?) claims :
1070          *      50% of all files are < 2.5K
1071          *      80% of all file accesses are to files < 10K
1072          *      40% of all file I/O is to files > 25K
1073          *
1074          * static examination of the files in our file system
1075          * seems to support the claim that 50% of all files are
1076          * smaller than 2.5K
1077          */
1078         if (bufs < 50)  {
1079                 bufs = (randnum % 3) + 1;
1080                 size = 1024;
1081         } else if (bufs < 97) {
1082                 bufs = (randnum % 6) + 1;
1083                 size = BUFSIZE;
1084         } else {
1085                 bufs = MAXFILESIZE;
1086                 size = BUFSIZE;
1087         }
1088
1089         for (wrote = 0; wrote < bufs; wrote++) {
1090                 if (write(fd, buf, size) == -1) {
1091                         error("write");
1092                         break;
1093                 }
1094         }
1095
1096         return (wrote);
1097 }
1098
1099 int
1100 op_null(void)
1101 {
1102
1103         return (1);
1104 }
1105
1106
1107 /*
1108  * Generate a getattr call by fstat'ing the current file
1109  * or by closing and re-opening it. This helps to keep the
1110  * attribute cache cold.
1111  */
1112 int
1113 op_getattr(void)
1114 {
1115         struct stat statb;
1116
1117         if ((random() % 2) == 0) {
1118                 (void) close(Openfd[Curnum]);
1119                 Openfd[Curnum] = 0;
1120                 if (openfile() == -1) {
1121                         return (0);
1122                 }
1123                 return (2);
1124         }
1125         if (fstat(Openfd[Curnum], &statb) == -1) {
1126                 error("getattr");
1127         }
1128         return (1);
1129 }
1130
1131
1132 int op_setattr(void)
1133 {
1134
1135         if (fchmod(Openfd[Curnum], 0666) == -1) {
1136                 error("setattr");
1137         }
1138         return (1);
1139 }
1140
1141
1142 int op_root(void)
1143 {
1144
1145         error("root");
1146         return (0);
1147 }
1148
1149
1150 /*
1151  * Generate a lookup by stat'ing the current name.
1152  */
1153 int op_lookup(void)
1154 {
1155         struct stat statb;
1156
1157         if (stat(Curname, &statb) == -1) {
1158                 error("lookup");
1159         }
1160         return (1);
1161 }
1162
1163
1164 int op_read(void)
1165 {
1166         int got;
1167         int bufs;
1168         int fd;
1169         char buf[BUFSIZE];
1170
1171         bufs = 0;
1172         fd = Openfd[Curnum];
1173
1174         if (lseek(fd, 0L, 0) == (off_t) -1) {
1175                 error("read: lseek");
1176                 return (0);
1177         }
1178
1179         while ((got = read(fd, buf, sizeof (buf))) > 0) {
1180                 bufs++;
1181         }
1182
1183         if (got == -1) {
1184                 error("read");
1185         } else {
1186                 bufs++;         /* did one extra read to find EOF */
1187         }
1188         return (bufs);
1189 }
1190
1191
1192 int op_wrcache(void)
1193 {
1194         error("wrcache");
1195         return 0;
1196 }
1197
1198
1199 int op_write(void)
1200 {
1201         int bufs;
1202
1203         bufs = writefile();
1204         if (bufs == 0) {
1205                 return (0);
1206         }
1207         (void) fsync(Openfd[Curnum]);
1208
1209         return (bufs + 2);
1210 }
1211
1212
1213 int op_create(void)
1214 {
1215
1216         if (createfile() == -1) {
1217                 return (0);
1218         }
1219         return (1);
1220 }
1221
1222
1223 int op_remove(void)
1224 {
1225         int fd;
1226         int got;
1227
1228         if (Linknum > 0) {
1229                 got = unlink(Linkname);
1230                 Linknum--;
1231                 (void) sprintf(Linkname, Otherspec, LINSTR, Linknum);
1232         } else if (Symnum > 1) {
1233                 got = unlink(Symname);
1234                 Symnum--;
1235                 (void) sprintf(Symname, Otherspec, SYMSTR, Symnum);
1236         } else {
1237                 fd = Openfd[Curnum];
1238
1239                 if (fd && (close(fd) == -1)) {
1240                         error("remove: close");
1241                 }
1242                 Openfd[Curnum] = 0;
1243                 got = unlink(Curname);
1244         }
1245         if (got == -1) {
1246                 error("remove");
1247         }
1248         return (1);
1249 }
1250
1251
1252 int toggle = 0;
1253
1254 int op_rename(void)
1255 {
1256         int got;
1257
1258         if (toggle++ & 01) {
1259                 got = rename(Rename2, Rename1);
1260         } else {
1261                 got = rename(Rename1, Rename2);
1262         }
1263         if (got == -1) {
1264                 error("rename");
1265         }
1266         return (1);
1267 }
1268
1269
1270 int op_link(void)
1271 {
1272
1273         Linknum++;
1274         (void) sprintf(Linkname, Otherspec, LINSTR, Linknum);
1275         if (link(Curname, Linkname) == -1) {
1276                 error("link");
1277         }
1278         return (1);
1279 }
1280
1281
1282 int op_readlink(void)
1283 {
1284         char    buf[MAXPATHLEN];
1285
1286         if (Symnum == 0) {
1287                 error("readlink");
1288                 return (0);
1289         }
1290         if (readlink(Symname, buf, sizeof (buf)) == -1) {
1291                 error("readlink");
1292         }
1293         return (1);
1294 }
1295
1296
1297 int op_symlink(void)
1298 {
1299
1300         Symnum++;
1301         (void) sprintf(Symname, Otherspec, SYMSTR, Symnum);
1302         if (symlink(Symlinkpath, Symname) == -1) {
1303                 error("symlink");
1304         }
1305         return (1);
1306 }
1307
1308
1309 int op_mkdir(void)
1310 {
1311
1312         Dirnum++;
1313         (void) sprintf(Dirname, Otherspec, DIRSTR, Dirnum);
1314         if (mkdir(Dirname, 0777) == -1) {
1315                 error("mkdir");
1316         }
1317         return (1);
1318 }
1319
1320
1321 int op_rmdir(void)
1322 {
1323
1324         if (Dirnum == 0) {
1325                 error("rmdir");
1326                 return (0);
1327         }
1328         if (rmdir(Dirname) == -1) {
1329                 error("rmdir");
1330         }
1331         Dirnum--;
1332         (void) sprintf(Dirname, Otherspec, DIRSTR, Dirnum);
1333         return (1);
1334 }
1335
1336
1337 int op_readdir(void)
1338 {
1339
1340         rewinddir(Testdir);
1341         while (readdir(Testdir) != (struct dirent *)NULL)
1342                 /* nothing */;
1343         return (1);
1344 }
1345
1346
1347 int op_fsstat(void)
1348 {
1349         struct statfs statfsb;
1350
1351         if (statfs(".", &statfsb) == -1) {
1352                 error("statfs");
1353         }
1354         return (1);
1355 }
1356
1357
1358 /*
1359  * Utility routines
1360  */
1361
1362 /*
1363  * Read counter arrays out of log file and accumulate them in "Optime"
1364  */
1365 void
1366 collect_counters(void)
1367 {
1368         int i;
1369         int j;
1370
1371         (void) lseek(Log, 0L, 0);
1372
1373         for (i = 0; i < Nprocs; i++) {
1374                 struct timeval buf[NOPS+1];
1375
1376                 if (read(Log, (char *)buf, sizeof (buf)) == -1) {
1377                         Saveerrno = errno;
1378                         (void) fprintf(stderr, "%s: can't read log ", Myname);
1379                         errno = Saveerrno;
1380                         perror("");
1381                         (void) kill(0, SIGINT);
1382                         exit(1);
1383                 }
1384
1385                 for (j = 0; j < NOPS+1; j++) {
1386                         addtime(Optime[j], buf[j]);
1387                 }
1388         }
1389 }
1390
1391 /*
1392  * Check consistance of results
1393  */
1394 int
1395 check_counters(void)
1396 {
1397         int i;
1398         int mixdiff;
1399         int got;
1400         int want;
1401
1402         mixdiff = 0;
1403         for (i = 0; i < NOPS; i++) {
1404                 got = Curct.calls[i] * 10000 / Curct.total;
1405                 want = Mix[i] * 100;
1406                 if (got > want) {
1407                         mixdiff += got - want;
1408                 } else {
1409                         mixdiff += want - got;
1410                 }
1411         }
1412         if (mixdiff > 1000) {
1413                 (void) fprintf(stdout,
1414                     "%s: INVALID RUN, mix generated is off by %d.%02d%%\n",
1415                     Myname, mixdiff / 100, mixdiff % 100);
1416                 return (-1);
1417         }
1418         return (0);
1419 }
1420
1421 /*
1422  * Print results
1423  */
1424 void
1425 print(void)
1426 {
1427         int totalmsec;
1428         int runtime;
1429         int msec;
1430         int i;
1431
1432         totalmsec = 0;
1433         for (i = 0; i < NOPS; i++) {
1434                 totalmsec += Optime[i].tv_sec * 1000;
1435                 totalmsec += Optime[i].tv_usec / 1000;
1436         }
1437
1438         if (Verbose) {
1439            const char *format = sizeof (Optime[0].tv_sec) == sizeof (long)
1440              ? "%-10s%3d%%    %2d.%02d%%   %6d   %4ld.%02ld    %4d.%02d    %2d.%02d%%\n"
1441              : "%-10s%3d%%    %2d.%02d%%   %6d   %4d.%02d    %4d.%02d    %2d.%02d%%\n";
1442                 (void) fprintf(stdout,
1443 "op        want       got    calls      secs  msec/call    time %%\n");
1444                 for (i = 0; i < NOPS; i++) {
1445                         msec = Optime[i].tv_sec * 1000
1446                             + Optime[i].tv_usec / 1000;
1447                         (void) fprintf(stdout, format,
1448                             Opnames[i], Mix[i],
1449                             Curct.calls[i] * 100 / Curct.total,
1450                             (Curct.calls[i] * 100 % Curct.total)
1451                                 * 100 / Curct.total,
1452                             Curct.calls[i],
1453                             Optime[i].tv_sec, Optime[i].tv_usec / 10000,
1454                             Curct.calls[i]
1455                                 ? msec / Curct.calls[i]
1456                                 : 0,
1457                             Curct.calls[i]
1458                                 ? (msec % Curct.calls[i]) * 100 / Curct.calls[i]
1459                                 : 0,
1460                             msec * 100 / totalmsec,
1461                             (msec * 100 % totalmsec) * 100 / totalmsec);
1462                 }
1463         }
1464
1465         runtime = Optime[NOPS].tv_sec / Nprocs;
1466         (void) fprintf(stdout,
1467             "%d sec %d calls %d.%02d calls/sec %d.%02d msec/call\n",
1468             runtime, Curct.total,
1469             Curct.total / runtime,
1470             ((Curct.total % runtime) * 100) / runtime,
1471             totalmsec / Curct.total,
1472             ((totalmsec % Curct.total) * 100) / Curct.total);
1473 }
1474
1475 /*
1476  * Use select to sleep for some number of milliseconds
1477  * granularity is 20 msec
1478  */
1479 void
1480 msec_sleep(int msecs)
1481 {
1482         struct timeval sleeptime;
1483
1484         if (msecs < 20) {
1485                 return;
1486         }
1487         sleeptime.tv_sec = msecs / 1000;
1488         sleeptime.tv_usec = (msecs % 1000) * 1000;
1489
1490         if (select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &sleeptime) == -1){
1491                 Saveerrno = errno;
1492                 (void) fprintf(stderr, "%s: select failed ", Myname);
1493                 errno = Saveerrno;
1494                 perror("");
1495                 (void) kill(0, SIGINT);
1496                 exit(1);
1497         }
1498 }
1499
1500 /*
1501  * Open the synchronization file with append mode
1502  */
1503 void
1504 init_logfile(void)
1505 {
1506
1507         (void) sprintf(Logname, "/tmp/nhfsstone%d", getpid());
1508         if ((Log = open(Logname, O_RDWR|O_CREAT|O_TRUNC|O_APPEND, 0666)) == -1){
1509                 Saveerrno = errno;
1510                 (void) fprintf(stderr,
1511                     "%s: can't open log file %s ", Myname, Logname);
1512                 errno = Saveerrno;
1513                 perror("");
1514                 exit(1);
1515         }
1516 }
1517
1518 /*
1519  * Zero counters
1520  */
1521 void
1522 init_counters(void)
1523 {
1524         int i;
1525
1526         Curct.total = 0;
1527         for (i = 0; i < NOPS; i++) {
1528                 Curct.calls[i] = 0;
1529                 Optime[i].tv_sec = 0;
1530                 Optime[i].tv_usec = 0;
1531         }
1532         Optime[NOPS].tv_sec = 0;
1533         Optime[NOPS].tv_usec = 0;
1534 }
1535
1536 /*
1537  * Set cur = cur - start
1538  */
1539 void
1540 get_delta(struct count *start, struct count *cur)
1541 {
1542         int i;
1543
1544         get_opct(cur);
1545         cur->total -= start->total;
1546         for (i = 0; i < NOPS; i++) {
1547                 cur->calls[i] -= start->calls[i];
1548         }
1549 }
1550
1551 /*
1552  * Read kernel stats
1553  */
1554 void
1555 get_opct(struct count *count)
1556 {
1557         static FILE     *fp = NULL;
1558         char            buffer[256];
1559         int i;
1560
1561         if (fp == NULL && !(fp = fopen("/proc/net/rpc/nfs", "r"))) {
1562                 perror("/proc/net/rpc/nfs");
1563                 (void) kill(0, SIGINT);
1564                 exit(1);
1565         } else {
1566                 fflush(fp);
1567                 rewind(fp);
1568         }
1569
1570         while (fgets(buffer, sizeof(buffer), fp) != NULL) {
1571                 char    *sp, *line = buffer;
1572
1573                 if ((sp = strchr(line, '\n')) != NULL)
1574                         *sp = '\0';
1575                 if (!(sp = strtok(line, " \t")) || strcmp(line, "proc2"))
1576                         continue;
1577                 if (!(sp = strtok(NULL, " \t")))
1578                         goto bummer;
1579                 count->total = 0;
1580                 for (i = 0; i < 18; i++) {
1581                         if (!(sp = strtok(NULL, " \t")))
1582                                 goto bummer;
1583                         /* printf("call %d -> %s\n", i, sp); */
1584                         count->calls[i] = atoi(sp);
1585                         count->total += count->calls[i];
1586                 }
1587                 /* printf("total calls %d\n", count->total); */
1588                 break;
1589         }
1590
1591         return;
1592
1593 bummer:
1594         fprintf(stderr, "parse error in /proc/net/rpc/nfs!\n");
1595         kill(0, SIGINT);
1596         exit(1);
1597 }
1598
1599 #define LINELEN         128             /* max bytes/line in mix file */
1600 #define MIX_START       0
1601 #define MIX_DATALINE    1
1602 #define MIX_DONE        2
1603 #define MIX_FIRSTLINE   3
1604
1605 /*
1606  * Mix file parser.
1607  * Assumes that the input file is in the same format as
1608  * the output of the nfsstat(8) command.
1609  *
1610  * Uses a simple state transition to keep track of what to expect.
1611  * Parsing is done a line at a time.
1612  *
1613  * State           Input                action          New state
1614  * MIX_START       ".*nfs:.*"           skip one line   MIX_FIRSTLINE
1615  * MIX_FIRSTLINE   ".*[0-9]*.*"         get ncalls      MIX_DATALINE
1616  * MIX_DATALINE    "[0-9]* [0-9]*%"X6   get op counts   MIX_DATALINE
1617  * MIX_DATALINE    "[0-9]* [0-9]*%"X4   get op counts   MIX_DONE
1618  * MIX_DONE        EOF                  return
1619  */
1620 int
1621 setmix(FILE *fp)
1622 {
1623         int state;
1624         int got;
1625         int opnum;
1626         int calls;
1627         int len;
1628         char line[LINELEN];
1629
1630         state = MIX_START;
1631         opnum = 0;
1632
1633         while (state != MIX_DONE && fgets(line, LINELEN, fp)) {
1634
1635                 switch (state) {
1636
1637                 case MIX_START:
1638                         len = strlen(line);
1639                         if (len >= 4 && substr(line, "nfs:")) {
1640                                 if (fgets(line, LINELEN, fp) == NULL) {
1641                                         (void) fprintf(stderr,
1642 "%s: bad mix format: unexpected EOF after 'nfs:'\n", Myname);
1643                                         return (-1);
1644                                 }
1645                                 state = MIX_FIRSTLINE;
1646                         }
1647                         break;
1648
1649                 case MIX_FIRSTLINE:
1650                         got = sscanf(line, "%d", &calls);
1651                         if (got != 1) {
1652                                 (void) fprintf(stderr,
1653 "%s: bad mix format: can't find 'calls' value %d\n", Myname, got);
1654                                 return (-1);
1655                         }
1656                         if (fgets(line, LINELEN, fp) == NULL) {
1657                                 (void) fprintf(stderr,
1658 "%s: bad mix format: unexpected EOF after 'calls'\n", Myname);
1659                                 return (-1);
1660                         }
1661                         state = MIX_DATALINE;
1662                         break;
1663
1664                 case MIX_DATALINE:
1665                         got = sscanf(line,
1666         "%d %*d%% %d %*d%% %d %*d%% %d %*d%% %d %*d%% %d %*d%% %d %*d%%",
1667         &Mix[opnum], &Mix[opnum+1], &Mix[opnum+2], &Mix[opnum+3],
1668         &Mix[opnum+4], &Mix[opnum+5], &Mix[opnum+6]);
1669                         if (got == 4 && opnum == 14) {
1670                                 /*
1671                                  * looks like the last line
1672                                  */
1673                                 state = MIX_DONE;
1674                         } else if (got == 7) {
1675                                 opnum += 7;
1676                                 if (fgets(line, LINELEN, fp) == NULL) {
1677                                         (void) fprintf(stderr,
1678 "%s: bad mix format: unexpected EOF after 'calls'\n", Myname);
1679                                         return (-1);
1680                                 }
1681                         } else {
1682                                 (void) fprintf(stderr,
1683 "%s: bad mix format: can't find %d op values\n", Myname, got);
1684                                 return (-1);
1685                         }
1686                         break;
1687                 default:
1688                         (void) fprintf(stderr,
1689                             "%s: unknown state %d\n", Myname, state);
1690                         return (-1);
1691                 }
1692         }
1693         if (state != MIX_DONE) {
1694                 (void) fprintf(stderr,
1695                     "%s: bad mix format: unexpected EOF\n", Myname);
1696                 return (-1);
1697         }
1698         for (opnum = 0; opnum < NOPS; opnum++) {
1699                 Mix[opnum] = Mix[opnum] * 100 / calls
1700                     + ((Mix[opnum] * 1000 / calls % 10) >= 5);
1701         }
1702         return (0);
1703 }
1704
1705 /*
1706  * return true if sp contains the substring subsp, false otherwise
1707  */
1708 int
1709 substr(char *sp, char *subsp)
1710 {
1711         int found;
1712         int want;
1713         char *s2;
1714
1715         if (sp == NULL || subsp == NULL) {
1716                 return (0);
1717         }
1718
1719         want = strlen(subsp);
1720
1721         while (*sp != '\0') {
1722                 while (*sp != *subsp && *sp != '\0') {
1723                         sp++;
1724                 }
1725                 found = 0;
1726                 s2 = subsp;
1727                 while (*sp == *s2) {
1728                         sp++;
1729                         s2++;
1730                         found++;
1731                 }
1732                 if (found == want) {
1733                         return (1);
1734                 }
1735         }
1736         return (0);
1737 }
1738
1739 /*
1740  * check to make sure that we have
1741  * both read and write permissions
1742  * for this file or directory.
1743  */
1744 int
1745 check_access(struct stat statb)
1746 {
1747         int gidsetlen;
1748         gid_t gidset[NGROUPS];
1749         int i;
1750
1751         if (statb.st_uid == getuid()) {
1752                 if ((statb.st_mode & 0200) && (statb.st_mode & 0400)) {
1753                         return 1;
1754                 } else {
1755                         return -1;
1756                 }
1757         }
1758
1759         gidsetlen = NGROUPS;
1760
1761         if (getgroups(gidsetlen, gidset) == -1) {
1762                 perror("getgroups");
1763                 return -1;
1764         }
1765
1766         for (i = 0; i < NGROUPS; i++) {
1767                 if (statb.st_gid == gidset[i]) {
1768                         if ((statb.st_mode & 020) && (statb.st_mode & 040)) {
1769                                 return 1;
1770                         } else {
1771                                 return -1;
1772                         }
1773                 }
1774         }
1775
1776         if ((statb.st_mode & 02) && (statb.st_mode & 04)) {
1777                 return 1;
1778         } else {
1779                 return -1;
1780         }
1781 }
1782
1783 void
1784 usage(void)
1785 {
1786
1787         (void) fprintf(stderr, "usage: %s [-v] [[-t secs] | [-c calls]] [-l load] [-p nprocs] [-m mixfile] [dir]...\n", Myname);
1788 }
1789
1790 void
1791 error(char *str)
1792 {
1793
1794         Saveerrno = errno;
1795         (void) fprintf(stderr, "%s: op failed: %s ", Myname, str);
1796         errno = Saveerrno;
1797         perror("");
1798 }