/*
* Program #1: time the effects of doing an access check at
* each read upon the performance of a program
*
* Author: Matt Bishop, UC Davis
* Version: 1.0
* Date: April 10, 2004
*/
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/stat.h>
/*
* Some systems, like MacOSX, don't have this defined;
* others, like Linux, do. So we handle both cases
*/
#ifndef RUSAGE_SELF
# define RUSAGE_SELF 0
#endif
/*
* Use these to process arguments to the program
*/
extern char *optarg;
extern int optind;
/*
* Useful constants
*/
#define DEF_ITER 1000000 /* default number of iterations */
#define DEF_BUFSIZ 10240 /* max buffer size */
/*
* Forward declarations
*/
void loopcum(int, int); /* iterate with check */
void loopsine(int, int); /* iterate without check */
double mdifftime(struct timeval, struct timeval); /* subtract times */
void err(int, char *); /* program error message printer */
void perr(int, char *); /* system error message printer */
/*
* Globally accessible variables
*/
char *progname = "*notset*"; /* name of program (for errors, etc) */
int blocksz; /* default size to read */
char *buf; /* buffer for input */
/*
* The main program
*/
int main(int argc, char *argv[])
{
int i; /* used to handle options to program */
int n = DEF_ITER; /* number of iterations */
int fd; /* descriptor of file to be used */
double cum, sine; /* time with, without checks */
struct stat stbuf; /* buffer for file attribute info */
struct rusage r1, r2, r3, r4; /* buffers for resources */
struct timeval tp1, tp2, tp3, tp4; /* wall clock times */
/*
* get program name for error messages and such
*/
progname = argv[0];
/*
* process arg list
*/
while ((i = getopt(argc, argv, "n:")) != EOF)
switch(i){
case 'n': /* number of iterations */
if ((n = atoi(optarg)) < 1)
err(EXIT_FAILURE, "-n needs argument");
break;
default: /* oops ... */
err(EXIT_FAILURE, "unknown option");
break;
}
/*
* see what we are to read, and yell if there's nothing there
* or there are too many files named
*/
if (optind == argc)
err(EXIT_FAILURE, "no file to use!");
if (optind != argc - 1)
err(EXIT_FAILURE, "at most 1 file can be used");
/*
* open the file and snarf the block size from the stat structure
*/
if ((fd = open(argv[optind], O_RDONLY)) < 0)
perr(EXIT_FAILURE, argv[optind]);
/* get the block size */
if (fstat(fd, &stbuf) < 0)
perr(EXIT_FAILURE, "fstat(initial)");
blocksz = stbuf.st_blksize;
/*
* allocate buffer
*/
if ((buf = malloc(blocksz * sizeof(char))) == NULL)
perr(EXIT_FAILURE, "malloc");
/*
************************
* without the access control check
************************
*/
/* Get the starting wall clock time and the user and system times */
if (gettimeofday(&tp1, NULL) < 0)
perr(EXIT_FAILURE, "gettimeofday(first call)");
if (getrusage(RUSAGE_SELF, &r1) < 0)
perr(EXIT_FAILURE, "getrusage(first call)");
/* run the test */
loopsine(fd, n);
/* Get the ending wall clock time and the user and system times */
if (getrusage(RUSAGE_SELF, &r2) < 0)
perr(EXIT_FAILURE, "getrusage(second call)");
if (gettimeofday(&tp2, NULL) < 0)
perr(EXIT_FAILURE, "gettimeofday(second call)");
/*
************************
* with the access control check
************************
*/
/* Get the starting wall clock time and the user and system times */
if (gettimeofday(&tp3, NULL) < 0)
perr(EXIT_FAILURE, "gettimeofday(third call)");
if (getrusage(RUSAGE_SELF, &r3) < 0)
perr(EXIT_FAILURE, "getrusage(third call)");
/* run the test */
loopcum(fd, n);
/* Get the ending wall clock time and the user and system times */
if (getrusage(RUSAGE_SELF, &r4) < 0)
perr(EXIT_FAILURE, "getrusage(fourth call)");
if (gettimeofday(&tp4, NULL) < 0)
perr(EXIT_FAILURE, "gettimeofday(fourth call)");
/*
* Print the results as a table
* In what follows, "sine" is the value without (sine) the check
* and "cum" is the value with (cum) the check
*/
/* print header */
printf("\t\tWITHOUT\t\t\tWITH\t\t\tDIFF\n");
/* print user times */
sine = mdifftime(r1.ru_utime, r2.ru_utime);
cum = mdifftime(r3.ru_utime, r4.ru_utime);
printf("USER\t%#18.6g\t%#18.6g\t%#18.6g\n",
sine / n, cum / n, (cum - sine) / n);
/* print system times */
sine = mdifftime(r1.ru_stime, r2.ru_stime);
cum = mdifftime(r3.ru_stime, r4.ru_stime);
printf("SYSTEM\t%#18.6g\t%#18.6g\t%#18.6g\n",
sine / n, cum / n, (cum - sine) / n);
/* print wall clock times */
sine = mdifftime(tp1, tp2);
cum = mdifftime(tp3, tp4);
printf("CLOCK\t%#18.6g\t%#18.6g\t%#18.6g\n",
sine / n, cum / n, (cum - sine) / n);
/*
* Clean up: close file, say goodnight, Dick!
*/
(void) close(fd);
return(EXIT_SUCCESS);
}
/*
* This returns the difference of two times as a floating point number
*
* CALL:
* double mdifftime(struct timeval t1, struct timeval t2)
* t1 subtrahend
* t2 minuend
*
* RETURNS:
* Double containing number of seconds in t2 - t1
*
* PRINTS:
* Nothing
*
* ERRORS:
* None
*
* SIDE EFFECTS:
* None
*/
double mdifftime(struct timeval t1, struct timeval t2)
{
long sec, usec; /* holds second, microsecond differences */
/* compute the differences of seconds and microseconds (usec) */
usec = t2.tv_usec - t1.tv_usec;
sec = t2.tv_sec - t1.tv_sec;
/* normalize */
if (usec < 0){
sec--;
usec += 1000000;
}
/* combine the values into a double */
return(((double) sec) + ((double) usec)/1000000.0);
}
/*
* This iterates n times a read of the first block of the file that
* fd names.
*
* CALL:
* void loopsine(int fd, int n)
* fd descriptor of file to be read (and checked)
* n number of times to iterate
*
* RETURNS:
* Nothing.
*
* PRINTS:
* On success, nothing
*
* ERRORS:
* read -- read system call fails
*
* SIDE EFFECTS:
* Reads file fd
* Alters contents of buffer buf
*/
void loopsine(int fd, int n)
{
register int i; /* counter in a for loop */
/*
* iterate as desired
*/
for(i = 0; i < n; i++){
/* go to first block */
(void) lseek(fd, 0, SEEK_SET);
/* read it in */
if (read(fd, buf, blocksz) < 0)
perr(EXIT_FAILURE, "read(sine)");
}
}
/*
* This iterates n times an access check followed by a read of the first
* block of the file that fd names. The access check determines if the
* process can read the file.
*
* CALL:
* void loopcum(int fd, int n)
* fd descriptor of file to be read (and checked)
* n number of times to iterate
*
* RETURNS:
* Nothing.
*
* PRINTS:
* On success, nothing
*
* ERRORS:
* getgroups -- getgroups system call fails
* fstat -- fstat system call fails
* read -- read system call fails
* file permission turned off during run
* == file can no longer be read!
*
* SIDE EFFECTS:
* Reads file fd
* Alters contents of buffer buf
*/
void loopcum(int fd, int n)
{
register int i, k; /* counters in for loops */
uid_t uid; /* effective UID (EUID) of process */
gid_t gid; /* effective GID (EGID) of process */
gid_t grplist[NGROUPS_MAX]; /* GIDs of secondary groups */
int gct; /* number of secondary groups */
struct stat stbuf; /* buffer for file attribute info */
unsigned int mask = 0; /* protection bitmask for read bit */
/*
* Get effective UID, primary effective GID, secondary groups
*/
uid = geteuid();
gid = getegid();
if ((gct = getgroups(NGROUPS_MAX, grplist)) < 0)
perr(EXIT_FAILURE, "getgroups");
/*
* iterate as desired
*/
for(i = 0; i < n; i++){
/*
* Access control check
*/
/* get the CURRENT file permission */
if (fstat(fd, &stbuf) < 0)
perr(EXIT_FAILURE, "fstat");
/*
* Check in this order:
* 1. If EUID is UID of file, check owner read bit
* (the 0400 bit)
* 2. Otherwise, if EGID is GID of file, check group
* read bit (the 0040 bit)
* 3. Otherwise, if ANY secondary group is GID of file,
* check group read bit (the 0040 bit, as above)
* 4. Otherwise, check the world read bit (the 0004 bit)
*/
/* Check EUID and file UID (step 1) */
if (uid == stbuf.st_uid)
mask = 0400;
/* Check EGID and file GID (step 2) */
else if (gid == stbuf.st_gid)
mask = 0040;
else{
/* Check secondary groups and file GID (step 3) */
for(k = 0; k < gct; k++)
if (grplist[k] == stbuf.st_gid)
break;
if (k != gct)
mask = 0040;
else
/* Check world bit (step 4) */
mask = 0004;
}
/* INCONSISTENCY: before the file could be read, */
/* now it can't be read -- what happened? BAIL!! */
if ((stbuf.st_mode&mask) != mask)
err(EXIT_FAILURE, "file permission turned off during run");
/*
* Okay to read the file, so do it
*/
/* go to first block */
(void) lseek(fd, 0, SEEK_SET);
/* read it in */
if (read(fd, buf, blocksz) < 0)
perr(EXIT_FAILURE, "read(cum)");
}
}
/*
* This prints the error message ermsg preceded by the program name.
* It then exits with exit code rv.
*
* CALL:
* void err(int rv, char *ermsg)
* rv exit code
* ermsg error message to be printed
*
* RETURNS:
* No return
*
* PRINTS:
* Given error message, preceded by progname and ": ", on stderr
*
* ERRORS:
* None.
*
* SIDE EFFECTS:
* Exits program
*/
void err(int rv, char *ermsg)
{
/* print program error message */
fprintf(stderr, "%s: %s\n", progname, ermsg);
/* bye-bye! */
exit(rv);
}
/*
* This prints an error message using ermsg as the string
* BEFORE the system error message. It then exits with
* exit code rv.
*
* CALL:
* void perr(int rv, char *ermsg)
* rv exit code
* ermsg error message to be printed
*
* RETURNS:
* No return
*
* PRINTS:
* System error message on stderr (using perror(3))
*
* ERRORS:
* None.
*
* SIDE EFFECTS:
* Exits program
*/
void perr(int rv, char *ermsg)
{
/* print system error message */
perror(ermsg);
/* bye-bye! */
exit(rv);
}