| /* |
| * |
| * Copyright (c) International Business Machines Corp., 2001 |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See |
| * the GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| /******************************************************************************/ |
| /* */ |
| /* File: mmstress.c */ |
| /* */ |
| /* Description: This is a test program that performs general stress with */ |
| /* memory race conditions. It contains seven testcases that */ |
| /* will test race conditions between simultaneous read fault, */ |
| /* write fault, copy on write (COW) fault e.t.c. */ |
| /* This testcase is intended to execute on the Linux operating */ |
| /* system and can be easily ported to work on other operating */ |
| /* systems as well. */ |
| /* */ |
| /* Usage: mmstress -h -n TEST NUMBER -p NPAGES -t EXECUTION TIME -v -V */ |
| /* -h - Help */ |
| /* -n TEST NUMBER - Execute a particular testcase */ |
| /* -p NPAGES - Use NPAGES pages for tests */ |
| /* -t EXECUTION TIME - Execute test for a certain time */ |
| /* -v - Verbose output */ |
| /* -V - Version of this program */ |
| /* */ |
| /* Author: Manoj Iyer - manjo@mail.utexas.edu */ |
| /* */ |
| /******************************************************************************/ |
| |
| /******************************************************************************/ |
| /* */ |
| /* Apr-13-2001 Created: Manoj Iyer, IBM Austin. */ |
| /* These tests are adapted from AIX vmm FVT tests. */ |
| /* */ |
| /* Oct-24-2001 Modified. */ |
| /* - freed buffers that were allocated. */ |
| /* - closed removed files. This will remove the disk full error */ |
| /* - use pthread_exit in case of theads instead of return. This */ |
| /* was really bad to use return! */ |
| /* - created usage function. */ |
| /* - pthread_join checks for thread exit status reported by */ |
| /* pthread_exit() */ |
| /* */ |
| /* Oct-25-2001 Modified. */ |
| /* - Fixed bug in usage() */ |
| /* - malloc'ed pointer for pthread return value. */ |
| /* - changed scheme. If no options are specified, all the tests */ |
| /* will be run once. */ |
| /* */ |
| /* Nov-02-2001 Modified - Paul Larson */ |
| /* - Added sched_yield to thread_fault to fix hang */ |
| /* - Removed thread_mmap */ |
| /* */ |
| /* Nov-09-2001 Modified - Manoj Iyer */ |
| /* - Removed compile warnings. */ |
| /* - Added missing header file. #include <stdlib.h> */ |
| /* */ |
| /* Oct-28-2003 Modified - Manoj Iyer */ |
| /* - missing parenthesis added. */ |
| /* - formatting changes. */ |
| /* - increased NUMPAGES to 9999. */ |
| /* */ |
| /* Jan-30-2003 Modified - Gary Williams */ |
| /* - fixed a race condition between the two threads */ |
| /* - made it so if any of the testcases fail the test will fail */ |
| /* - fixed so status of child in test 6 is used to determine result */ |
| /* - fixed the use of the remove_files function in a conditional */ |
| /* */ |
| /******************************************************************************/ |
| |
| #include <stdio.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <sys/mman.h> |
| #include <sys/wait.h> |
| #include <sys/time.h> |
| #include <pthread.h> |
| #include <signal.h> |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sched.h> |
| #include <stdint.h> |
| #include <getopt.h> |
| |
| #include "test.h" |
| |
| /* GLOBAL DEFINES */ |
| #define SIGENDSIG -1 /* end of signal marker */ |
| #define THNUM 0 /* array element pointing to number of threads */ |
| #define MAPADDR 1 /* array element pointing to map address */ |
| #define PAGESIZ 2 /* array element pointing to page size */ |
| #define FLTIPE 3 /* array element pointing to fault type */ |
| #define READ_FAULT 0 /* instructs routine to simulate read fault */ |
| #define WRITE_FAULT 1 /* instructs routine to simulate write fault */ |
| #define COW_FAULT 2 /* instructs routine to simulate copy-on-write fault */ |
| #define NUMTHREAD 32 /* number of threads to spawn default to 32 */ |
| #define NUMPAGES 9999 /* default (random) value of number of pages */ |
| #ifndef TRUE |
| #define TRUE 1 |
| #endif |
| #ifndef FALSE |
| #define FALSE 0 |
| #endif |
| #define FAILED (-1) /* return status for all funcs indicating failure */ |
| #define SUCCESS 0 /* return status for all routines indicating success */ |
| |
| #define MAXTEST 6 /* total number of testcase in this program */ |
| #define BRKSZ 512*1024 /* program data space allocation value */ |
| |
| static volatile int wait_thread; /* used to wake up sleeping threads */ |
| static volatile int thread_begin; /* used to coordinate threads */ |
| static int verbose_print = FALSE; /* print more test information */ |
| |
| static int pages_num = NUMPAGES; /* number of pages to use for tests */ |
| static volatile int alarm_fired; |
| |
| char *TCID = "mmstress"; |
| int TST_TOTAL = 6; |
| |
| static void sig_handler(int signal) |
| { |
| if (signal != SIGALRM) { |
| fprintf(stderr, |
| "sig_handlder(): unexpected signal caught [%d]\n", |
| signal); |
| exit(TBROK); |
| } |
| |
| alarm_fired = 1; |
| } |
| |
| static void usage(char *progname) |
| { |
| fprintf(stderr, "usage:%s -h -n test -t time -v [-V]\n", progname); |
| fprintf(stderr, "\t-h displays all options\n"); |
| fprintf(stderr, "\t-n test number, if no test number\n" |
| "\t is specified, all the tests will be run\n"); |
| fprintf(stderr, "\t-p specify the number of pages to\n" |
| "\t use for allocation\n"); |
| fprintf(stderr, "\t-t specify the time in hours\n"); |
| fprintf(stderr, "\t-v verbose output\n"); |
| fprintf(stderr, "\t-V program version\n"); |
| exit(1); |
| } |
| |
| static void set_timer(int run_time) |
| { |
| struct itimerval timer; |
| |
| memset(&timer, 0, sizeof(struct itimerval)); |
| timer.it_interval.tv_usec = 0; |
| timer.it_interval.tv_sec = 0; |
| timer.it_value.tv_usec = 0; |
| timer.it_value.tv_sec = (time_t) (run_time * 3600.0); |
| |
| if (setitimer(ITIMER_REAL, &timer, NULL)) { |
| perror("set_timer(): setitimer()"); |
| exit(1); |
| } |
| } |
| |
| /******************************************************************************/ |
| /* */ |
| /* Function: thread_fault */ |
| /* */ |
| /* Description: Executes as a thread function and accesses the memory pages */ |
| /* depending on the fault_type to be generated. This function */ |
| /* can cause READ fault, WRITE fault, COW fault. */ |
| /* */ |
| /* Input: void *args - argments passed to the exec routine by */ |
| /* pthread_create() */ |
| /* */ |
| /******************************************************************************/ |
| static void *thread_fault(void *args) |
| { |
| long *local_args = args; /* local pointer to list of arguments */ |
| /* local_args[THNUM] - the thread number */ |
| /* local_args[MAPADDR] - map address */ |
| /* local_args[PAGESIZ] - page size */ |
| /* local_args[FLTIPE] - fault type */ |
| int pgnum_ndx = 0; /* index to the number of pages */ |
| char *start_addr /* start address of the page */ |
| = (void *) (local_args[MAPADDR] |
| + (int)local_args[THNUM] |
| * (pages_num / NUMTHREAD) |
| * local_args[PAGESIZ]); |
| char read_from_addr = 0; /* address to which read from page is done */ |
| char write_to_addr[] = { 'a' }; /* character to be writen to the page */ |
| |
| /*************************************************************/ |
| /* The way it was, args could be overwritten by subsequent uses |
| * of it before this routine had a chance to use the data. |
| * This flag stops the overwrite until this routine gets to |
| * here. At this point, it is done initializing and it is |
| * safe for the parent thread to continue (which will change |
| * args). |
| */ |
| thread_begin = FALSE; |
| |
| while (wait_thread) |
| sched_yield(); |
| |
| for (; pgnum_ndx < (pages_num / NUMTHREAD); pgnum_ndx++) { |
| /* if the fault to be generated is READ_FAULT, read from the page */ |
| /* else write a character to the page. */ |
| ((int)local_args[3] == READ_FAULT) ? (read_from_addr = |
| *start_addr) |
| : (*start_addr = write_to_addr[0]); |
| start_addr += local_args[PAGESIZ]; |
| if (verbose_print) |
| tst_resm(TINFO, |
| "thread_fault(): generating fault type %ld" |
| " @page address %p", local_args[3], |
| start_addr); |
| fflush(NULL); |
| } |
| pthread_exit(NULL); |
| } |
| |
| /******************************************************************************/ |
| /* */ |
| /* Function: remove_tmpfiles */ |
| /* */ |
| /* Description: remove temporary files that were created by the tests. */ |
| /* */ |
| /******************************************************************************/ |
| static int remove_files(char *filename, char *addr) |
| { |
| if (addr) |
| if (munmap(addr, sysconf(_SC_PAGESIZE) * pages_num) < 0) { |
| perror("map_and_thread(): munmap()"); |
| return FAILED; |
| } |
| if (strcmp(filename, "NULL") && strcmp(filename, "/dev/zero")) { |
| if (unlink(filename)) { |
| perror("map_and_thread(): ulink()"); |
| return FAILED; |
| } |
| } else { |
| if (verbose_print) |
| tst_resm(TINFO, "file %s removed", filename); |
| |
| } |
| return SUCCESS; |
| } |
| |
| /******************************************************************************/ |
| /* */ |
| /* Function: map_and_thread */ |
| /* */ |
| /* Description: Creates mappings with the required properties, of MAP_PRIVATE */ |
| /* MAP_SHARED and of PROT_RED / PROT_READ|PROT_WRITE. */ |
| /* Create threads and execute a routine that will generate the */ |
| /* desired fault condition, viz, read, write and cow fault. */ |
| /* */ |
| /* Input: char *tmpfile - name of temporary file that is created */ |
| /* int fault_type - type of fault that is to be generated. */ |
| /* */ |
| /******************************************************************************/ |
| int map_and_thread(char *tmpfile, |
| void *(*exec_func) (void *), |
| int fault_type, |
| int num_thread) |
| { |
| int fd = 0; /* file descriptor of the file created */ |
| int thrd_ndx = 0; /* index to the number of threads created */ |
| int map_type = 0; /* specifies the type of the mapped object */ |
| void *th_status; /* status of the thread when it is finished */ |
| long th_args[5]; /* argument list passed to thread_fault() */ |
| char *empty_buf = NULL; /* empty buffer used to fill temp file */ |
| long pagesize /* contains page size at runtime */ |
| = sysconf(_SC_PAGESIZE); |
| static pthread_t pthread_ids[NUMTHREAD]; |
| /* contains ids of the threads created */ |
| void * map_addr = NULL; /* address where the file is mapped */ |
| |
| /* Create a file with permissions 0666, and open it with RDRW perms */ |
| /* if the name is not a NULL */ |
| |
| if (strcmp(tmpfile, "NULL")) { |
| if ((fd = |
| open(tmpfile, O_RDWR | O_CREAT, |
| S_IRWXO | S_IRWXU | S_IRWXG)) |
| == -1) { |
| perror("map_and_thread(): open()"); |
| close(fd); |
| fflush(NULL); |
| return FAILED; |
| } |
| |
| /* Write pagesize * pages_num bytes to the file */ |
| empty_buf = malloc(pagesize * pages_num); |
| if (write(fd, empty_buf, pagesize * pages_num) != |
| (pagesize * pages_num)) { |
| perror("map_and_thread(): write()"); |
| free(empty_buf); |
| fflush(NULL); |
| remove_files(tmpfile, NULL); |
| close(fd); |
| return FAILED; |
| } |
| map_type = (fault_type == COW_FAULT) ? MAP_PRIVATE : MAP_SHARED; |
| |
| /* Map the file, if the required fault type is COW_FAULT map the file */ |
| /* private, else map the file shared. if READ_FAULT is required to be */ |
| /* generated map the file with read protection else map with read - */ |
| /* write protection. */ |
| |
| if ((map_addr = (void *) mmap(0, pagesize * pages_num, |
| ((fault_type == READ_FAULT) ? |
| PROT_READ : PROT_READ | |
| PROT_WRITE), map_type, fd, 0)) |
| == MAP_FAILED) { |
| perror("map_and_thread(): mmap()"); |
| free(empty_buf); |
| fflush(NULL); |
| remove_files(tmpfile, NULL); |
| close(fd); |
| return FAILED; |
| } else { |
| if (verbose_print) |
| tst_resm(TINFO, |
| "map_and_thread(): mmap success, address = %p", |
| map_addr); |
| fflush(NULL); |
| } |
| } |
| |
| /* As long as wait is set to TRUE, the thread that will be created will */ |
| /* loop in its exec routine */ |
| |
| wait_thread = TRUE; |
| |
| /* Create a few threads, ideally number of threads equals number of CPU'S */ |
| /* so that we can assume that each thread will run on a single CPU in */ |
| /* of SMP machines. Currently we will create NR_CPUS number of threads. */ |
| |
| th_args[1] = (long)map_addr; |
| th_args[2] = pagesize; |
| th_args[3] = fault_type; |
| do { |
| th_args[0] = thrd_ndx; |
| th_args[4] = (long)0; |
| |
| /*************************************************************/ |
| /* The way it was, args could be overwritten by subsequent uses |
| * of it before the called routine had a chance to fully initialize. |
| * This flag stops the overwrite until that routine gets to |
| * begin. At that point, it is done initializing and it is |
| * safe for the this thread to continue (which will change |
| * args). |
| * A basic race condition. |
| */ |
| thread_begin = TRUE; |
| if (pthread_create(&pthread_ids[thrd_ndx++], NULL, exec_func, |
| (void *)&th_args)) { |
| perror("map_and_thread(): pthread_create()"); |
| thread_begin = FALSE; |
| free(empty_buf); |
| fflush(NULL); |
| remove_files(tmpfile, map_addr); |
| close(fd); |
| return FAILED; |
| } else { |
| /***************************************************/ |
| /* Yield until new thread is done with args. |
| */ |
| while (thread_begin) |
| sched_yield(); |
| } |
| } while (thrd_ndx < num_thread); |
| |
| if (verbose_print) |
| tst_resm(TINFO, "map_and_thread(): pthread_create() success"); |
| wait_thread = FALSE; |
| |
| /* suspend the execution of the calling thread till the execution of the */ |
| /* other thread has been terminated. */ |
| |
| for (thrd_ndx = 0; thrd_ndx < NUMTHREAD; thrd_ndx++) { |
| if (pthread_join(pthread_ids[thrd_ndx], &th_status)) { |
| perror("map_and_thread(): pthread_join()"); |
| free(empty_buf); |
| fflush(NULL); |
| remove_files(tmpfile, map_addr); |
| close(fd); |
| return FAILED; |
| } else { |
| if ((long)th_status == 1) { |
| tst_resm(TINFO, |
| "thread [%ld] - process exited with errors", |
| (long)pthread_ids[thrd_ndx]); |
| free(empty_buf); |
| remove_files(tmpfile, map_addr); |
| close(fd); |
| exit(1); |
| } |
| } |
| } |
| |
| /* remove the temporary file that was created. - clean up */ |
| /* but dont try to remove special files. */ |
| |
| /***********************************************/ |
| /* Was if !(remove_files()) ... |
| * If that routine succeeds, it returns SUCCESS, which |
| * happens to be 0. So if the routine succeeded, the |
| * above condition would indicate failure. This change |
| * fixes that. |
| */ |
| if (remove_files(tmpfile, map_addr) == FAILED) { |
| free(empty_buf); |
| return FAILED; |
| } |
| |
| free(empty_buf); |
| close(fd); |
| return SUCCESS; |
| } |
| |
| /******************************************************************************/ |
| /* */ |
| /* Test: Test case tests the race condition between simultaneous read */ |
| /* faults in the same address space. */ |
| /* */ |
| /* Description: map a file into memory, create threads and execute a thread */ |
| /* function that will cause read faults by simultaneously reading*/ |
| /* from this memory space. */ |
| /******************************************************************************/ |
| static int test1(void) |
| { |
| tst_resm(TINFO, "test1: Test case tests the race condition between " |
| "simultaneous read faults in the same address space."); |
| return map_and_thread("./tmp.file.1", thread_fault, READ_FAULT, NUMTHREAD); |
| } |
| |
| /******************************************************************************/ |
| /* */ |
| /* Test: Test case tests the race condition between simultaneous write */ |
| /* faults in the same address space. */ |
| /* */ |
| /* Description: map a file into memory, create threads and execute a thread */ |
| /* function that will cause write faults by simultaneously */ |
| /* writing to this memory space. */ |
| /******************************************************************************/ |
| static int test2(void) |
| { |
| tst_resm(TINFO, "test2: Test case tests the race condition between " |
| "simultaneous write faults in the same address space."); |
| return map_and_thread("./tmp.file.2", thread_fault, WRITE_FAULT, NUMTHREAD); |
| } |
| |
| /******************************************************************************/ |
| /* */ |
| /* Test: Test case tests the race condition between simultaneous COW */ |
| /* faults in the same address space. */ |
| /* */ |
| /* Description: map a file into memory, create threads and execute a thread */ |
| /* function that will cause COW faults by simultaneously */ |
| /* writing to this memory space. */ |
| /* */ |
| /******************************************************************************/ |
| static int test3(void) |
| { |
| tst_resm(TINFO, "test3: Test case tests the race condition between " |
| "simultaneous COW faults in the same address space."); |
| return map_and_thread("./tmp.file.3", thread_fault, COW_FAULT, NUMTHREAD); |
| } |
| |
| /******************************************************************************/ |
| /* */ |
| /* Test: Test case tests the race condition between simultaneous READ */ |
| /* faults in the same address space. File mapped is /dev/zero */ |
| /* */ |
| /* Description: Map a file into memory, create threads and execute a thread */ |
| /* function that will cause READ faults by simultaneously */ |
| /* writing to this memory space. */ |
| /* */ |
| /******************************************************************************/ |
| static int test4(void) |
| { |
| tst_resm(TINFO, "test4: Test case tests the race condition between " |
| "simultaneous READ faults in the same address space. " |
| "The file mapped is /dev/zero"); |
| return map_and_thread("/dev/zero", thread_fault, COW_FAULT, NUMTHREAD); |
| } |
| |
| /******************************************************************************/ |
| /* */ |
| /* Test: Test case tests the race condition between simultaneous */ |
| /* fork - exit faults in the same address space. */ |
| /* */ |
| /* Description: Initialize large data in the parent process, fork a child and */ |
| /* and the parent waits for the child to complete execution. */ |
| /* */ |
| /******************************************************************************/ |
| static int test5(void) |
| { |
| int fork_ndx = 0; |
| pid_t pid = 0; |
| int wait_status = 0; |
| |
| tst_resm(TINFO, "test5: Test case tests the race condition between " |
| "simultaneous fork - exit faults in the same address space."); |
| |
| /* increment the program's data space by 200*1024 (BRKSZ) bytes */ |
| |
| if (sbrk(BRKSZ) == (void *) - 1) { |
| perror("test5(): sbrk()"); |
| fflush(NULL); |
| return FAILED; |
| } |
| |
| /* fork NUMTHREAD number of processes, assumption is on SMP each will get */ |
| /* a separate CPU if NRCPUS = NUMTHREAD. The child does nothing; exits */ |
| /* immediately, parent waits for child to complete execution. */ |
| do { |
| if (!(pid = fork())) |
| _exit(0); |
| else { |
| if (pid != -1) |
| wait(&wait_status); |
| } |
| |
| } while (fork_ndx++ < NUMTHREAD); |
| |
| if (sbrk(-BRKSZ) == (void *) - 1) { |
| tst_resm(TINFO, "test5(): rollback sbrk failed"); |
| fflush(NULL); |
| perror("test5(): sbrk()"); |
| fflush(NULL); |
| return FAILED; |
| } |
| return SUCCESS; |
| } |
| |
| /******************************************************************************/ |
| /* */ |
| /* Test: Test case tests the race condition between simultaneous */ |
| /* fork - exec - exit faults in the same address space. */ |
| /* */ |
| /* Description: Initialize large data in the parent process, fork a child and */ |
| /* and the parent waits for the child to complete execution. The */ |
| /* child program execs a dummy program. */ |
| /* */ |
| /******************************************************************************/ |
| static int test6(void) |
| { |
| int res = SUCCESS; |
| int fork_ndx = 0; |
| pid_t pid = 0; |
| int wait_status; |
| char *argv_init[2] = { "arg1", NULL }; |
| |
| tst_resm(TINFO, "test6: Test case tests the race condition between " |
| "simultaneous fork -exec - exit faults in the same address space."); |
| |
| /* increment the program's data space by 200*1024 (BRKSZ) bytes */ |
| if (sbrk(BRKSZ) == (void *) - 1) { |
| perror("test6(): sbrk()"); |
| fflush(NULL); |
| return FAILED; |
| } |
| |
| /* fork NUMTHREAD number of processes, assumption is on SMP each will get */ |
| /* a separate CPU if NRCPUS = NUMTHREAD. The child execs a dummy program */ |
| /* and parent waits for child to complete execution. */ |
| do { |
| if (!(pid = fork())) { |
| if (execvp("mmstress_dummy", argv_init) == -1) { |
| if (execvp("./mmstress_dummy", argv_init) == -1) { |
| perror("test6(): execvp()"); |
| fflush(NULL); |
| exit(99); |
| } |
| } |
| } else { |
| if (pid != -1) |
| wait(&wait_status); |
| |
| if (WEXITSTATUS(wait_status) != 0) |
| res = FAILED; |
| } |
| |
| } while (fork_ndx++ < NUMTHREAD); |
| |
| if (sbrk(-BRKSZ) == (void *) - 1) { |
| tst_resm(TINFO, "test6(): rollback sbrk failed"); |
| fflush(NULL); |
| perror("test6(): sbrk()"); |
| fflush(NULL); |
| return FAILED; |
| } |
| |
| return res; |
| } |
| |
| static int (*(test_ptr)[]) () = {test1, test2, test3, test4, test5, test6}; |
| |
| static void run_test(unsigned int i) |
| { |
| int rc; |
| |
| rc = test_ptr[i](); |
| |
| if (rc == SUCCESS) |
| tst_resm(TPASS, "TEST %d Passed", i + 1); |
| else |
| tst_resm(TFAIL, "TEST %d Failed", i + 1); |
| |
| if (alarm_fired) |
| tst_exit(); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| static char *version_info = "mmstress V1.00 04/17/2001"; |
| int ch, i; |
| int test_num = 0; |
| int test_time = 0; |
| int run_once = TRUE; |
| |
| static struct signal_info { |
| int signum; |
| char *signame; |
| } sig_info[] = { |
| {SIGHUP, "SIGHUP"}, |
| {SIGINT, "SIGINT"}, |
| {SIGQUIT, "SIGQUIT"}, |
| {SIGABRT, "SIGABRT"}, |
| {SIGBUS, "SIGBUS"}, |
| {SIGSEGV, "SIGSEGV"}, |
| {SIGALRM, "SIGALRM"}, |
| {SIGUSR1, "SIGUSR1"}, |
| {SIGUSR2, "SIGUSR2"}, |
| {SIGENDSIG, "ENDSIG"} |
| }; |
| |
| setvbuf(stdout, NULL, _IONBF, 0); |
| setvbuf(stderr, NULL, _IONBF, 0); |
| |
| if (argc < 2) |
| tst_resm(TINFO, "run %s -h for all options", argv[0]); |
| |
| while ((ch = getopt(argc, argv, "hn:p:t:vV")) != -1) { |
| switch (ch) { |
| case 'h': |
| usage(argv[0]); |
| break; |
| case 'n': |
| test_num = atoi(optarg); |
| break; |
| case 'p': |
| pages_num = atoi(optarg); |
| break; |
| case 't': |
| tst_resm(TINFO, |
| "Test is scheduled to run for %d hours", |
| test_time = atoi(optarg)); |
| run_once = FALSE; |
| break; |
| case 'v': |
| verbose_print = TRUE; |
| break; |
| case 'V': |
| tst_resm(TINFO, "%s: %s", argv[0], version_info); |
| break; |
| case '?': |
| fprintf(stderr, |
| "%s: unknown option - %c ignored\n", |
| argv[0], optopt); |
| break; |
| default: |
| tst_brkm(TBROK, NULL, "%s: getopt() failed!!!\n", |
| argv[0]); |
| } |
| } |
| |
| set_timer(test_time); |
| |
| for (i = 0; sig_info[i].signum != -1; i++) { |
| if (signal(sig_info[i].signum, sig_handler) == SIG_ERR) { |
| tst_brkm(TBROK | TERRNO, NULL, "signal(%s) failed", |
| sig_info[i].signame); |
| } |
| } |
| |
| tst_tmpdir(); |
| |
| do { |
| if (!test_num) { |
| for (i = 0; i < MAXTEST; i++) |
| run_test(i); |
| } else { |
| if (test_num >= MAXTEST) { |
| tst_brkm(TBROK, NULL, "Invalid test number %i", |
| test_num); |
| } |
| |
| run_test(test_num); |
| } |
| } while (!run_once); |
| |
| tst_rmdir(); |
| tst_exit(); |
| } |