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