/* * 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); }