From: Thomas Veerman Date: Tue, 14 Jul 2009 09:43:33 +0000 (+0000) Subject: Added select test X-Git-Tag: v3.1.5~212 X-Git-Url: http://zhaoyanbai.com/repos/%22/xml/v3/zones/static/Bv9ARM.ch05.html?a=commitdiff_plain;h=70d25344a217b924ba326f74781c7b6647e53e37;p=minix.git Added select test --- diff --git a/test/Makefile b/test/Makefile index af1b8d98a..5f788ced7 100644 --- a/test/Makefile +++ b/test/Makefile @@ -7,7 +7,7 @@ OBJ= test1 test2 test3 test4 test5 test6 test7 test8 test9 \ test10 test12 test13 test14 test15 test16 test17 test18 test19 \ test21 test22 test23 test25 test26 test27 test28 test29 \ test30 test31 test32 test34 test35 test36 test37 test38 \ - test39 t10a t11a t11b + test39 t10a t11a t11b test40 t40a t40b t40c t40d t40e t40f BIGOBJ= test20 test24 ROOTOBJ= test11 test33 @@ -28,7 +28,7 @@ $(ROOTOBJ): clean: cd select && make clean - -rm -rf *.o *.s *.bak test? test?? t10a t11a t11b DIR* + -rm -rf *.o *.s *.bak test? test?? t10a t11a t11b t40a t40b t40c t40d t40e t40f DIR* test1: test1.c test2: test2.c @@ -72,3 +72,10 @@ test36: test36.c test37: test37.c test38: test38.c test39: test39.c +test40: test40.c +t40a: t40a.c +t40b: t40b.c +t40c: t40c.c +t40d: t40d.c +t40e: t40e.c +t40f: t40f.c diff --git a/test/run b/test/run index aee52bbac..30ca39b5d 100755 --- a/test/run +++ b/test/run @@ -12,12 +12,12 @@ badones= # list of tests that failed # Print test welcome message clr -echo "Running POSIX compliance test suite. There are 39 tests in total." +echo "Running POSIX compliance test suite. There are 42 tests in total." echo " " # Run all the tests, keeping track of who failed. for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 \ - 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 \ + 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \ sh1.sh sh2.sh do total=`expr $total + 1` if ./test$i diff --git a/test/t40a.c b/test/t40a.c new file mode 100644 index 000000000..8dcbc7bef --- /dev/null +++ b/test/t40a.c @@ -0,0 +1,141 @@ +/* t40a.c + * + * Test FD_* macros + * + * Select works on regular files, (pseudo) terminal devices, streams-based + * files, FIFOs, pipes, and sockets. This test verifies the FD_* macros. + * + * This test is part of a bigger select test. It expects as argument which sub- + * test it is. + */ + +#include +#include +#include +#include +#include +#include + +#ifndef OPEN_MAX +# define OPEN_MAX 1024 +#endif + +#define MAX_ERROR 5 + +int errct = 0, subtest = -1; + +void e(int n, char *s) { + printf("Subtest %d, error %d, %s\n", subtest, n, s); + + if (errct++ > MAX_ERROR) { + printf("Too many errors; test aborted\n"); + exit(errct); + } +} + +int main(int argc, char **argv) { + fd_set fds; + int i; + + /* Get subtest number */ + if(argc != 2) { + printf("Usage: %s subtest_no\n", argv[0]); + exit(-1); + } else if(sscanf(argv[1], "%d", &subtest) != 1) { + printf("Usage: %s subtest_no\n", argv[0]); + exit(-1); + } + + /* FD_ZERO */ + FD_ZERO(&fds); + for(i = 0; i < OPEN_MAX; i++) { + if(FD_ISSET(i, &fds)) { + e(1, "fd set should be completely empty"); + break; + } + } + + /* FD_SET */ + for(i = 0; i < OPEN_MAX; i++) FD_SET(i, &fds); + for(i = 0; i < OPEN_MAX; i++) { + if(!FD_ISSET(i, &fds)) { + e(2, "fd set should be completely filled"); + break; + } + } + + /* Reset to empty set and verify it's really empty */ + FD_ZERO(&fds); + for(i = 0; i < OPEN_MAX; i++) { + if(FD_ISSET(i, &fds)) { + e(3, "fd set should be completely empty"); + break; + } + } + + /* Let's try a variation on filling the set */ + for(i = 0; i < OPEN_MAX; i += 2) FD_SET(i, &fds); + for(i = 0; i < OPEN_MAX - 1; i+= 2 ) { + if(!(FD_ISSET(i, &fds) && !FD_ISSET(i+1, &fds))) { + e(4, "bit pattern does not match"); + break; + } + } + + /* Reset to empty set and verify it's really empty */ + FD_ZERO(&fds); + for(i = 0; i < OPEN_MAX; i++) { + if(FD_ISSET(i, &fds)) { + e(5,"fd set should be completely empty"); + break; + } + } + + /* Let's try another variation on filling the set */ + for(i = 0; i < OPEN_MAX - 1; i += 2) FD_SET(i+1, &fds); + for(i = 0; i < OPEN_MAX - 1; i+= 2 ) { + if(!(FD_ISSET(i+1, &fds) && !FD_ISSET(i, &fds))) { + e(6, "bit pattern does not match"); + break; + } + } + + /* Reset to empty set and verify it's really empty */ + FD_ZERO(&fds); + for(i = 0; i < OPEN_MAX; i++) { + if(FD_ISSET(i, &fds)) { + e(7, "fd set should be completely empty"); + break; + } + } + + /* FD_CLR */ + for(i = 0; i < OPEN_MAX; i++) FD_SET(i, &fds); /* Set all bits */ + for(i = 0; i < OPEN_MAX; i++) FD_CLR(i, &fds); /* Clear all bits */ + for(i = 0; i < OPEN_MAX; i++) { + if(FD_ISSET(i, &fds)) { + e(8, "all bits in fd set should be cleared"); + break; + } + } + + /* Reset to empty set and verify it's really empty */ + FD_ZERO(&fds); + for(i = 0; i < OPEN_MAX; i++) { + if(FD_ISSET(i, &fds)) { + e(9, "fd set should be completely empty"); + break; + } + } + + for(i = 0; i < OPEN_MAX; i++) FD_SET(i, &fds); /* Set all bits */ + for(i = 0; i < OPEN_MAX; i += 2) FD_CLR(i, &fds); /* Clear all bits */ + for(i = 0; i < OPEN_MAX; i += 2) { + if(FD_ISSET(i, &fds)) { + e(10, "all even bits in fd set should be cleared"); + break; + } + } + + exit(errct); +} diff --git a/test/t40b.c b/test/t40b.c new file mode 100644 index 000000000..a23abaacb --- /dev/null +++ b/test/t40b.c @@ -0,0 +1,148 @@ +/* t40b.c + * + * Test regular files + * + * Select works on regular files, (pseudo) terminal devices, streams-based + * files, FIFOs, pipes, and sockets. This test verifies selecting for regular + * file descriptors. "File descriptors associated with regular files shall + * always select true for ready to read, ready to write, and error conditions" + * - Open Group. Although we set a timeout, the select should return + * immediately. + * + * This test is part of a bigger select test. It expects as argument which sub- + * test it is. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FILE1 "/tmp/selecttest01-1" +#define FILES 2 +#define TIME 3 + +#define MAX_ERROR 10 + +int errct = 0, subtest = -1; +char errorbuf[1000]; + +void e(int n, char *s) { + printf("Subtest %d, error %d, %s\n", subtest, n, s); + + if (errct++ > MAX_ERROR) { + printf("Too many errors; test aborted\n"); + exit(errct); + } +} + +int main(int argc, char **argv) { + int fd1, fd2, retval; + fd_set fds_read, fds_write, fds_error; + struct timeval tv; + time_t start, end; + + /* Get subtest number */ + if(argc != 2) { + printf("Usage: %s subtest_no\n", argv[0]); + exit(-1); + } else if(sscanf(argv[1], "%d", &subtest) != 1) { + printf("Usage: %s subtest_no\n", argv[0]); + exit(-1); + } + + /* Set timeout */ + tv.tv_sec = TIME; + tv.tv_usec = 0; + + /* Open a file for writing */ + if((fd1 = open(FILE1, O_WRONLY|O_CREAT, 0644)) == -1) { + snprintf(errorbuf, sizeof(errorbuf), "failed to open file %s for writing", + FILE1); + e(1, errorbuf); + perror(NULL); + exit(1); + } + + /* Open the same file for reading */ + if((fd2 = open(FILE1, O_RDONLY)) == -1) { + snprintf(errorbuf, sizeof(errorbuf), "failed to open file %s for reading", + FILE1); + e(2, errorbuf); + perror(NULL); + exit(1); + } + + /* Clear file descriptor bit masks */ + FD_ZERO(&fds_read); FD_ZERO(&fds_write); FD_ZERO(&fds_error); + + /* Fill bit mask */ + FD_SET(fd1, &fds_write); + FD_SET(fd2, &fds_read); + FD_SET(fd1, &fds_error); + FD_SET(fd2, &fds_error); + + /* Do the select and time how long it takes */ + start = time(NULL); + retval = select(fd2+1, &fds_read, &fds_write, &fds_error, &tv); + end = time(NULL); + + /* Correct amount of ready file descriptors? 1 read + 1 write + 2 errors */ + if(retval != 4) { + e(3, "four fds should be set"); + } + + /* Test resulting bit masks */ + if(!FD_ISSET(fd1, &fds_write)) e(4, "write should be set"); + if(!FD_ISSET(fd2, &fds_read)) e(5, "read should be set"); + if(!FD_ISSET(fd1, &fds_error)) e(6, "error should be set"); + if(!FD_ISSET(fd2, &fds_error)) e(7, "error should be set"); + + /* Was it instantaneous? */ + if(end-start != TIME - TIME) { + snprintf(errorbuf,sizeof(errorbuf),"time spent blocking is not %d, but %ld", + TIME - TIME, (long int) (end-start)); + e(8, "time spent blocking is not %d, but %ld"); + } + + /* Wait for read to become ready on O_WRONLY. This should fail immediately. */ + FD_ZERO(&fds_read); FD_ZERO(&fds_write); FD_ZERO(&fds_error); + FD_SET(fd1, &fds_read); + FD_SET(fd1, &fds_error); + FD_SET(fd2, &fds_error); + tv.tv_sec = TIME; + tv.tv_usec = 0; + retval = select(fd2+1, &fds_read, NULL, &fds_error, &tv); + + /* Correct amount of ready file descriptors? 1 read + 2 error */ + if(retval != 3) e(9, "incorrect amount of ready file descriptors"); + if(!FD_ISSET(fd1, &fds_read)) e(10, "read should be set"); + if(!FD_ISSET(fd1, &fds_error)) e(11, "error should be set"); + if(!FD_ISSET(fd2, &fds_error)) e(12, "error should be set"); + + /* Try again as above, bit this time with O_RDONLY in the write set */ + FD_ZERO(&fds_error); + FD_SET(fd2, &fds_write); + FD_SET(fd1, &fds_error); + FD_SET(fd2, &fds_error); + tv.tv_sec = TIME; + tv.tv_usec = 0; + retval = select(fd2+1, NULL, &fds_write, &fds_error, &tv); + + /* Correct amount of ready file descriptors? 1 write + 2 errors */ + if(retval != 3) e(13, "incorrect amount of ready file descriptors"); + if(!FD_ISSET(fd2, &fds_write)) e(14, "write should be set"); + if(!FD_ISSET(fd1, &fds_error)) e(15, "error should be set"); + if(!FD_ISSET(fd2, &fds_error)) e(16, "error should be set"); + + close(fd1); + close(fd2); + unlink(FILE1); + + exit(errct); +} diff --git a/test/t40c.c b/test/t40c.c new file mode 100644 index 000000000..c51622909 --- /dev/null +++ b/test/t40c.c @@ -0,0 +1,148 @@ +/* t40c.c + * + * Test (pseudo) terminal devices + * + * Select works on regular files, (pseudo) terminal devices, streams-based + * files, FIFOs, pipes, and sockets. This test verifies selecting for (pseudo) + * terminal devices. + * + * This test is part of a bigger select test. It expects as argument which sub- + * test it is. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TERMINALW "/dev/ttyp0" +#define TERMINALR "/dev/ptyp0" +#define SENDSTRING "minixrocks" +#define MAX_ERROR 5 + +int errct = 0, subtest = -1; + +void e(int n, char *s) { + printf("Subtest %d, error %d, %s\n", subtest, n, s); + + if (errct++ > MAX_ERROR) { + printf("Too many errors; test aborted\n"); + exit(errct); + } +} + +int do_child(void) { + int fd, retval; + struct timeval tv; + + /* Opening master terminal for writing */ + if((fd = open(TERMINALW, O_WRONLY)) == -1) { + printf("Error opening %s for writing, signalling parent to quit\n", + TERMINALW); + perror(NULL); + printf("Please make sure that %s is not in use while running this test\n", + TERMINALW); + exit(-1); + } + + /* Going to sleep for two seconds to allow the parent proc to get ready */ + tv.tv_sec = 2; + tv.tv_usec = 0; + select(0, NULL, NULL, NULL, &tv); + + /* Try to write. Doesn't matter how many bytes we actually send. */ + retval = write(fd, SENDSTRING, strlen(SENDSTRING)); + close(fd); + + /* Wait for another second to allow the parent to process incoming data */ + tv.tv_usec = 1000000; + retval = select(0,NULL, NULL, NULL, &tv); + exit(0); +} + +int do_parent(int child) { + int fd; + fd_set fds_read, fds_write, fds_error; + int retval; + + /* Open slave terminal for reading */ + if((fd = open(TERMINALR, O_RDONLY)) == -1) { + printf("Error opening %s for reading\n", TERMINALR); + perror(NULL); + printf("Please make sure that %s is not in use while running this test.\n", + TERMINALR); + waitpid(child, &retval, 0); + exit(-1); + } + + /* Clear bit masks */ + FD_ZERO(&fds_read); FD_ZERO(&fds_write); FD_ZERO(&fds_error); + /* Set read bits */ + FD_SET(fd, &fds_read); + FD_SET(fd, &fds_write); + + /* Test if we can read or write from/to fd. As fd is opened read only we + * cannot actually write, so the select should return immediately with fd + * set in fds_write, but not in fds_read. Note that the child waits two + * seconds before sending data. This gives us the opportunity run this + * sub-test as reading from fd is blocking at this point. */ + retval = select(fd+1, &fds_read, &fds_write, &fds_error, NULL); + + if(retval != 1) e(1, "incorrect amount of ready file descriptors"); + + + if(FD_ISSET(fd, &fds_read)) e(2, "read should NOT be set"); + if(!FD_ISSET(fd, &fds_write)) e(3, "write should be set"); + if(FD_ISSET(fd, &fds_error)) e(4, "error should NOT be set"); + + /* Block until ready; until child wrote stuff */ + FD_ZERO(&fds_read); FD_ZERO(&fds_write); FD_ZERO(&fds_error); + FD_SET(fd, &fds_read); + retval = select(fd+1, &fds_read, NULL, &fds_error, NULL); + + if(retval != 1) e(5, "incorrect amount of ready file descriptors"); + if(!FD_ISSET(fd, &fds_read)) e(6, "read should be set"); + if(FD_ISSET(fd, &fds_error)) e(7, "error should not be set"); + + + FD_ZERO(&fds_read); FD_ZERO(&fds_error); + FD_SET(fd,&fds_write); + retval = select(fd+1, NULL, &fds_write, NULL, NULL); + /* As it is impossible to write to a read only fd, this select should return + * immediately with fd set in fds_write. */ + if(retval != 1) e(8, "incorrect amount or ready file descriptors"); + + close(fd); + waitpid(child, &retval, 0); + exit(errct); +} + +int main(int argc, char **argv) { + int forkres; + + /* Get subtest number */ + if(argc != 2) { + printf("Usage: %s subtest_no\n", argv[0]); + exit(-1); + } else if(sscanf(argv[1], "%d", &subtest) != 1) { + printf("Usage: %s subtest_no\n", argv[0]); + exit(-1); + } + + forkres = fork(); + if(forkres == 0) do_child(); + else if(forkres > 0) do_parent(forkres); + else { /* Fork failed */ + perror("Unable to fork"); + exit(-1); + } + + exit(-2); /* We're not supposed to get here. Both do_* routines should exit*/ + +} diff --git a/test/t40d.c b/test/t40d.c new file mode 100644 index 000000000..ffda1bb95 --- /dev/null +++ b/test/t40d.c @@ -0,0 +1,428 @@ +/* t40d.c + * + * Test FIFOs and pipes + * + * Select works on regular files, (pseudo) terminal devices, streams-based + * files, FIFOs, pipes, and sockets. This test verifies selecting for FIFOs + * (named pipes) and pipes (anonymous pipes). This test will not verify most + * error file descriptors, as the setting of this fdset in the face of an error + * condition is implementation-specific (except for regular files (alway set) + * and sockets (protocol-specific or OOB data received), but those file types + * are not being tested in this specific test). + * + * This test is part of a bigger select test. It expects as argument which sub- + * test it is. + * + * [1] If a socket has a pending error, it shall be considered to have an + * exceptional condition pending. Otherwise, what constitutes an exceptional + * condition is file type-specific. For a file descriptor for use with a + * socket, it is protocol-specific except as noted below. For other file types + * it is implementation-defined. If the operation is meaningless for a + * particular file type, pselect() or select() shall indicate that the + * descriptor is ready for read or write operations, and shall indicate that + * the descriptor has no exceptional condition pending. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NAMEDPIPE1 "/tmp/selecttest03-1" +#define NAMEDPIPE2 "/tmp/selecttest03-2" +#define SENDSTRING "minixrocks" +#define DO_HANDLEDATA 1 +#define DO_PAUSE 3 +#define DO_TIMEOUT 7 +#define MAX_ERROR 5 + +int errct = 0, subtest = -1; +char errbuf[1000]; +int fd_ap[2]; /* Anonymous pipe; read from fd_ap[0], write to fd_ap[1] */ +int fd_np1; /* Named pipe */ +int fd_np2; /* Named pipe */ + +void e(int n, char *s) { + printf("Subtest %d, error %d, %s\n", subtest, n, s); + + if (errct++ > MAX_ERROR) { + printf("Too many errors; test aborted\n"); + exit(errct); + } +} + +void do_child(void) { + struct timeval tv; + int retval; + + /* Open named pipe for writing. This will block until a reader arrives. */ + if((fd_np1 = open(NAMEDPIPE1, O_WRONLY)) == -1) { + printf("Error opening %s for writing, signalling parent to quit\n", + NAMEDPIPE1); + perror(NULL); + printf("Please make sure that %s is not in use while running this test\n", + NAMEDPIPE1); + exit(-1); + } + + /* Going to sleep for three seconds to allow the parent proc to get ready */ + tv.tv_sec = DO_HANDLEDATA; + tv.tv_usec = 0; + select(0, NULL, NULL, NULL, &tv); + + /* Try to write. Doesn't matter how many bytes we actually send. */ + retval = write(fd_np1, SENDSTRING, strlen(SENDSTRING)); + + /* Wait for another second to allow the parent to process incoming data */ + tv.tv_sec = DO_HANDLEDATA; + tv.tv_usec = 0; + retval = select(0,NULL, NULL, NULL, &tv); + + close(fd_np1); + + /* Wait for another second to allow the parent to process incoming data */ + tv.tv_sec = DO_HANDLEDATA; + tv.tv_usec = 0; + retval = select(0,NULL, NULL, NULL, &tv); + + /* Open named pipe for reading. This will block until a writer arrives. */ + if((fd_np2 = open(NAMEDPIPE2, O_RDONLY)) == -1) { + printf("Error opening %s for reading, signalling parent to quit\n", + NAMEDPIPE2); + perror(NULL); + printf("Please make sure that %s is not in use while running this test\n", + NAMEDPIPE2); + exit(-1); + } + + /* Wait for another second to allow the parent to run some tests. */ + tv.tv_sec = DO_HANDLEDATA; + tv.tv_usec = 0; + retval = select(0, NULL, NULL, NULL, &tv); + + close(fd_np2); + + /* Anonymous pipe */ + + /* Let the parent do initial read and write tests from and to the pipe. */ + tv.tv_sec = DO_PAUSE; + tv.tv_usec = 0; + retval = select(0, NULL, NULL, NULL, &tv); + + /* Unblock blocking read select by writing data */ + if(write(fd_ap[1], SENDSTRING, strlen(SENDSTRING)) < 0) { + perror("Could not write to anonymous pipe"); + exit(-1); + } + + exit(0); +} + +int count_fds(int nfds, fd_set *fds) { + /* Return number of bits set in fds */ + int i, result = 0; + assert(fds != NULL && nfds > 0); + for(i = 0; i < nfds; i++) { + if(FD_ISSET(i, fds)) result++; + } + return result; +} + +int empty_fds(int nfds, fd_set *fds) { + /* Returns nonzero if the first bits up to nfds in fds are not set */ + int i; + assert(fds != NULL && nfds > 0); + for(i = 0; i < nfds; i++) if(FD_ISSET(i, fds)) return 0; + return 1; +} + +int compare_fds(int nfds, fd_set *lh, fd_set *rh) { + /* Returns nonzero if lh equals rh up to nfds bits */ + int i; + assert(lh != NULL && rh != NULL && nfds > 0); + for(i = 0; i < nfds; i++) { + if((FD_ISSET(i, lh) && !FD_ISSET(i, rh)) || + (!FD_ISSET(i, lh) && FD_ISSET(i, rh))) { + return 0; + } + } + return 1; +} + +void dump_fds(int nfds, fd_set *fds) { + /* Print a graphical representation of bits in fds */ + int i; + if(fds != NULL && nfds > 0) { + for(i = 0; i < nfds; i++) printf("%d ", (FD_ISSET(i, fds) ? 1 : 0)); + printf("\n"); + } +} + +void do_parent(int child) { + fd_set fds_read, fds_write, fds_error; + fd_set fds_compare_read, fds_compare_write; + struct timeval tv; + time_t start, end; + int retval; + char buf[20]; + + /* Open named pipe for reading. This will block until a writer arrives. */ + if((fd_np1 = open(NAMEDPIPE1, O_RDONLY)) == -1) { + printf("Error opening %s for reading\n", NAMEDPIPE1); + perror(NULL); + printf("Please make sure that %s is not in use while running this test.\n", + NAMEDPIPE1); + waitpid(child, &retval, 0); + exit(-1); + } + + /* Clear bit masks */ + FD_ZERO(&fds_read); FD_ZERO(&fds_write); + /* Set read and write bits */ + FD_SET(fd_np1, &fds_read); + FD_SET(fd_np1, &fds_write); + tv.tv_sec = DO_TIMEOUT; + tv.tv_usec = 0; + + /* Test if we can read or write from/to fd_np1. As fd_np1 is opened read only + * we cannot actually write, so the select should return immediately [1] and + * the offending bit set in the fd set. We read from a pipe that is opened + * with O_NONBLOCKING cleared, so it is guaranteed we can read. + * However, at this moment the writer is sleeping, so the pipe is empty and + * read is supposed to block. Therefore, only 1 file descriptor should be + * ready. A timeout value is still set in case an error occurs in a faulty + * implementation. */ + retval = select(fd_np1+1, &fds_read, &fds_write, NULL, &tv); + + /* Did we receive an error? */ + if(retval <= 0) { + snprintf(errbuf, sizeof(errbuf), + "one fd should be set%s", (retval == 0 ? " (TIMEOUT)" : "")); + e(1, errbuf); + } + + if(!empty_fds(fd_np1+1,&fds_read)) e(2, "no read bits should be set"); + + + /* Make sure the write bit is set (and just 1 bit) */ + FD_ZERO(&fds_compare_write); FD_SET(fd_np1, &fds_compare_write); + if(!compare_fds(fd_np1+1, &fds_compare_write, &fds_write)) + e(3, "write should be set"); + + /* Clear sets and set up new bit masks */ + FD_ZERO(&fds_read); FD_ZERO(&fds_write); + FD_SET(fd_np1, &fds_read); + tv.tv_sec = DO_TIMEOUT; /* To make sure we get to see some error messages + instead of blocking forever when the + implementation is faulty. A timeout causes retval + to be 0. */ + tv.tv_usec = 0; + /* The sleeping writer is about to wake up and write data to the pipe. */ + retval = select(fd_np1+1, &fds_read, &fds_write, NULL, &tv); + + /* Correct amount of ready file descriptors? Just 1 read */ + if(retval != 1) { + snprintf(errbuf, sizeof(errbuf), + "one fd should be set%s", (retval == 0 ? " (TIMEOUT)" : "")); + e(4, errbuf); + } + + if(!FD_ISSET(fd_np1, &fds_read)) e(5, "read should be set"); + + /* Note that we left the write set empty. This should be equivalent to + * setting this parameter to NULL. */ + if(!empty_fds(fd_np1+1, &fds_write)) e(6, "write should NOT be set"); + + /* In case something went wrong above, we might end up with a child process + * blocking on a write call we close the file descriptor now. Synchronize on + * a read. */ + if(read(fd_np1, buf, sizeof(SENDSTRING)) < 0) perror("Read error"); + + /* Close file descriptor. We're going to reverse the test */ + close(fd_np1); + + /* Wait for a second to allow the child to close the pipe as well */ + tv.tv_sec = DO_HANDLEDATA; + tv.tv_usec = 0; + retval = select(0,NULL, NULL, NULL, &tv); + + /* Open named pipe for writing. This call blocks until a reader arrives. */ + if((fd_np2 = open(NAMEDPIPE2, O_WRONLY)) == -1) { + printf("Error opening %s for writing\n", + NAMEDPIPE2); + perror(NULL); + printf("Please make sure that %s is not in use while running this test\n", + NAMEDPIPE2); + exit(-1); + } + + /* At this moment the child process has opened the named pipe for reading and + * we have opened it for writing. We're now going to reverse some of the + * tests we've done earlier. */ + + /* Clear sets and set up bit masks */ + FD_ZERO(&fds_read); FD_ZERO(&fds_write); FD_ZERO(&fds_error); + FD_SET(fd_np2, &fds_read); + FD_SET(fd_np2, &fds_write); + tv.tv_sec = DO_TIMEOUT; + tv.tv_usec = 0; + /* Select for reading from an fd opened O_WRONLY. This should return + * immediately as it is not a meaningful operation [1] and is therefore not + * blocking. The select should return two file descriptors are ready (the + * failing read and valid write). */ + + retval = select(fd_np2+1, &fds_read, &fds_write, &fds_error, &tv); + + /* Did we receive an error? */ + if(retval <= 0) { + snprintf(errbuf, sizeof(errbuf), + "two fds should be set%s", (retval == 0 ? " (TIMEOUT)" : "")); + e(7, errbuf); + } + + /* Make sure read bit is set (and just 1 bit) */ + FD_ZERO(&fds_compare_read); FD_SET(fd_np2, &fds_compare_read); + if(!compare_fds(fd_np2+1, &fds_compare_read, &fds_read)) + e(8, "read should be set"); + + /* Write bit should be set (and just 1 bit) */ + FD_ZERO(&fds_compare_write); FD_SET(fd_np2, &fds_compare_write); + if(!compare_fds(fd_np2+1, &fds_compare_write, &fds_write)) + e(9, "write should be set"); + + if(!empty_fds(fd_np2+1, &fds_error)) + e(10, "Error should NOT be set"); + + FD_ZERO(&fds_read); FD_ZERO(&fds_write); + FD_SET(fd_np2, &fds_write); + tv.tv_sec = DO_TIMEOUT; + tv.tv_usec = 0; + retval = select(fd_np2+1, &fds_read, &fds_write, NULL, &tv); + + /* Correct amount of ready file descriptors? Just 1 write */ + if(retval != 1) { + snprintf(errbuf, sizeof(errbuf), + "one fd should be set%s", (retval == 0 ? " (TIMEOUT)" : "")); + e(11, errbuf); + } + + if(!empty_fds(fd_np2+1, &fds_read)) e(12, "read should NOT be set"); + + /* Anonymous pipe */ + + /* Check if we can write to the pipe */ + FD_ZERO(&fds_read); FD_ZERO(&fds_write); + FD_SET(fd_ap[1], &fds_write); + tv.tv_sec = DO_TIMEOUT; + tv.tv_usec = 0; + retval = select(fd_ap[1]+1, NULL, &fds_write, NULL, &tv); + + /* Correct amount of ready file descriptors? Just 1 write */ + if(retval != 1) { + snprintf(errbuf, sizeof(errbuf), + "one fd should be set%s", (retval == 0 ? " (TIMEOUT)" : "")); + e(13, errbuf); + } + + /* Make sure write bit is set (and just 1 bit) */ + FD_ZERO(&fds_compare_write); FD_SET(fd_ap[1], &fds_compare_write); + if(!compare_fds(fd_ap[1]+1, &fds_compare_write, &fds_write)) + e(14, "write should be set"); + + /* Intentionally test reading from pipe and letting it time out. */ + FD_SET(fd_ap[0], &fds_read); + tv.tv_sec = 1; + tv.tv_usec = 0; + start = time(NULL); + retval = select(fd_ap[0]+1, &fds_read, NULL, NULL, &tv); + end = time(NULL); + + /* Did we time out? */ + if(retval != 0) e(15, "we should have timed out"); + + /* Did it take us approximately 1 second? */ + if((int) (end - start) != 1) { + snprintf(errbuf, sizeof(errbuf), + "time out is not 1 second (instead, it is %ld)", + (long int) (end - start)); + e(16, errbuf); + } + + /* Do another read, but this time we expect incoming data from child. */ + FD_ZERO(&fds_read); + FD_SET(fd_ap[0], &fds_read); + tv.tv_sec = DO_TIMEOUT; + tv.tv_usec = 0; + retval = select(fd_ap[0]+1, &fds_read, NULL, NULL, &tv); + + /* Correct amount of ready file descriptors? Just 1 read. */ + if(retval != 1) e(17, "one fd should be set"); + + /* Is the read bit set? And just 1 bit. */ + FD_ZERO(&fds_compare_read); FD_SET(fd_ap[0], &fds_compare_read); + if(!compare_fds(fd_ap[0]+1, &fds_compare_read, &fds_read)) + e(18, "read should be set."); + + /* By convention fd_ap[0] is meant to be used for reading from the pipe and + * fd_ap[1] is meant for writing, where fd_ap is a an anonymous pipe. + * However, it is unspecified what happens when fd_ap[0] is used for writing + * and fd_ap[1] for reading. (It is unsupported on Minix.) As such, it is not + * necessary to make test cases for wrong pipe file descriptors using select. + */ + + waitpid(child, &retval, 0); + unlink(NAMEDPIPE2); + unlink(NAMEDPIPE1); + exit(errct); +} + +int main(int argc, char **argv) { + int forkres; + + /* Get subtest number */ + if(argc != 2) { + printf("Usage: %s subtest_no\n", argv[0]); + exit(-2); + } else if(sscanf(argv[1], "%d", &subtest) != 1) { + printf("Usage: %s subtest_no\n", argv[0]); + exit(-2); + } + + /* Set up anonymous pipe */ + if(pipe(fd_ap) < 0) { + perror("Could not create anonymous pipe"); + exit(-1); + } + + /* Create named pipe2. It is unlinked by do_parent. */ + if(mkfifo(NAMEDPIPE1, 0600) < 0) { + printf("Could not create named pipe %s", NAMEDPIPE1); + perror(NULL); + exit(-1); + } + + if(mkfifo(NAMEDPIPE2, 0600) < 0) { + printf("Could not create named pipe %s", NAMEDPIPE2); + perror(NULL); + exit(-1); + } + + forkres = fork(); + if(forkres == 0) do_child(); + else if(forkres > 0) do_parent(forkres); + else { /* Fork failed */ + perror("Unable to fork"); + exit(-1); + } + + exit(-2); /* We're not supposed to get here. Both do_* routines should exit*/ + +} diff --git a/test/t40e.c b/test/t40e.c new file mode 100644 index 000000000..5a5ba4c39 --- /dev/null +++ b/test/t40e.c @@ -0,0 +1,429 @@ +/* t40e.c + * + * Test sockets + * + * Select works on regular files, (pseudo) terminal devices, streams-based + * files, FIFOs, pipes, and sockets. This test verifies selecting for sockets. + * + * This test is part of a bigger select test. It expects as argument which sub- + * test it is. + * + * Specific rules for sockets: + * If a socket has a pending error, it shall be considered to have an + * exceptional condition pending. Otherwise, what constitutes an exceptional + * condition is file type-specific. For a file descriptor for use with a + * socket, it is protocol-specific except as noted below. For other file types + * it is implementation-defined. If the operation is meaningless for a + * particular file type, pselect() or select() shall indicate that the + * descriptor is ready for read or write operations, and shall indicate that + * the descriptor has no exceptional condition pending. + * + * [1] If a descriptor refers to a socket, the implied input function is the + * recvmsg()function with parameters requesting normal and ancillary data, such + * that the presence of either type shall cause the socket to be marked as + * readable. The presence of out-of-band data shall be checked if the socket + * option SO_OOBINLINE has been enabled, as out-of-band data is enqueued with + * normal data. If the socket is currently listening, then it shall be marked + * as readable if an incoming connection request has been received, and a call + * to the accept() function shall complete without blocking. + * + * [2] If a descriptor refers to a socket, the implied output function is the + * sendmsg() function supplying an amount of normal data equal to the current + * value of the SO_SNDLOWAT option for the socket. If a non-blocking call to + * the connect() function has been made for a socket, and the connection + * attempt has either succeeded or failed leaving a pending error, the socket + * shall be marked as writable. + * + * [3] A socket shall be considered to have an exceptional condition pending if + * a receive operation with O_NONBLOCK clear for the open file description and + * with the MSG_OOB flag set would return out-of-band data without blocking. + * (It is protocol-specific whether the MSG_OOB flag would be used to read + * out-of-band data.) A socket shall also be considered to have an exceptional + * condition pending if an out-of-band data mark is present in the receive + * queue. Other circumstances under which a socket may be considered to have an + * exceptional condition pending are protocol-specific and + * implementation-defined. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DO_HANDLEDATA 1 +#define DO_PAUSE 3 +#define DO_TIMEOUT 7 +#define MYPORT 3490 +#define NUMCHILDREN 5 +#define MAX_ERROR 10 + +int errct = 0, subtest = -1; +char errbuf[1000]; + +void e(int n, char *s) { + printf("Subtest %d, error %d, %s\n", subtest, n, s); + + if (errct++ > MAX_ERROR) { + printf("Too many errors; test aborted\n"); + exit(errct); + } +} + +/* All *_fds routines are helping routines. They intentionally use FD_* macros + in order to prevent making assumptions on how the macros are implemented.*/ + +int count_fds(int nfds, fd_set *fds) { + /* Return number of bits set in fds */ + int i, result = 0; + assert(fds != NULL && nfds > 0); + for(i = 0; i < nfds; i++) { + if(FD_ISSET(i, fds)) result++; + } + return result; +} + +int empty_fds(int nfds, fd_set *fds) { + /* Returns nonzero if the first bits up to nfds in fds are not set */ + int i; + assert(fds != NULL && nfds > 0); + for(i = 0; i < nfds; i++) if(FD_ISSET(i, fds)) return 0; + return 1; +} + +int compare_fds(int nfds, fd_set *lh, fd_set *rh) { + /* Returns nonzero if lh equals rh up to nfds bits */ + int i; + assert(lh != NULL && rh != NULL && nfds > 0); + for(i = 0; i < nfds; i++) { + if((FD_ISSET(i, lh) && !FD_ISSET(i, rh)) || + (!FD_ISSET(i, lh) && FD_ISSET(i, rh))) { + return 0; + } + } + return 1; +} + +void dump_fds(int nfds, fd_set *fds) { + /* Print a graphical representation of bits in fds */ + int i; + if(fds != NULL && nfds > 0) { + for(i = 0; i < nfds; i++) printf("%d ", (FD_ISSET(i, fds) ? 1 : 0)); + printf("\n"); + } +} + +void do_child(int childno) { + int fd_sock, port; + int retval; + + fd_set fds_read, fds_write, fds_error; + fd_set fds_compare_write; + + struct hostent *he; + struct sockaddr_in server; + struct timeval tv; + + if((fd_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) { + perror("Error getting socket\n"); + exit(-1); + } + + if((he = gethostbyname("127.0.0.1")) == NULL){/*"localhost" might be unknown*/ + perror("Error resolving"); + exit(-1); + } + + /* Child 4 connects to the wrong port. See Actual testing description below.*/ + port = (childno == 3 ? MYPORT + 1 : MYPORT); + + memcpy(&server.sin_addr, he->h_addr_list[0], he->h_length); + server.sin_family = AF_INET; + server.sin_port = htons(port); + +#if 0 + printf("Going to connect to: %s:%d\n", inet_ntoa(server.sin_addr), + ntohs(server.sin_port)); +#endif + + /* Normally we'd zerofill sin_zero, but there is no such thing on Minix */ +#ifndef _MINIX + memset(server.sin_zero, '\0', sizeof server.sin_zero); +#endif + + /* Wait for parent to set up connection */ + tv.tv_sec = (childno <= 1 ? DO_PAUSE : DO_TIMEOUT); + tv.tv_usec = 0; + retval = select(0, NULL, NULL, NULL, &tv); + + /* All set, let's do some testing */ + /* Children 3 and 4 do a non-blocking connect */ + if(childno == 2 || childno == 3) + fcntl(fd_sock, F_SETFL, fcntl(fd_sock, F_GETFL, 0) | O_NONBLOCK); + + if(connect(fd_sock, (struct sockaddr *) &server, sizeof(server)) < 0) { + /* Well, we don't actually care. The connect is non-blocking and is + supposed to "in progress" at this point. */ + } + + if(childno == 2 || childno == 3) { /* Children 3 and 4 */ + /* Open Group: "If a non-blocking call to the connect() function has been + made for a socket, and the connection attempt has either succeeded or + failed leaving a pending error, the socket shall be marked as writable. + ... + A socket shall be considered to have an exceptional condition pending if + a receive operation with O_NONBLOCK clear for the open file description + and with the MSG_OOB flag set would return out-of-band data without + blocking. (It is protocol-specific whether the MSG_OOB flag would be used + to read out-of-band data.) A socket shall also be considered to have an + exceptional condition pending if an out-of-band data mark is present in + the receive queue. Other circumstances under which a socket may be + considered to have an exceptional condition pending are protocol-specific + and implementation-defined." + + In other words, it only makes sense for us to check the write set as the + read set is not expected to be set, but is allowed to be set (i.e., + unspecified) and whether the error set is set is implementation-defined. + */ + FD_ZERO(&fds_read); FD_ZERO(&fds_write); FD_ZERO(&fds_error); + FD_SET(fd_sock, &fds_write); + tv.tv_sec = DO_TIMEOUT; + tv.tv_usec = 0; + retval = select(fd_sock+1, NULL, &fds_write, NULL, &tv); + + + if(retval <= 0) e(6, "expected one fd to be ready"); + + FD_ZERO(&fds_compare_write); FD_SET(fd_sock, &fds_compare_write); + if(!compare_fds(fd_sock+1, &fds_compare_write, &fds_compare_write)) + e(7, "write should be set"); + } + + if(close(fd_sock) < 0) { + perror("Error disconnecting"); + exit(-1); + } + + exit(errct); +} + +void do_parent(void) { + int fd_sock, fd_new, yes = 1, exitstatus; + int sockets[NUMCHILDREN], i; + fd_set fds_read, fds_write, fds_error; + fd_set fds_compare_read, fds_compare_write; + struct timeval tv; + int retval, childresults = 0; + + struct sockaddr_in my_addr; + struct sockaddr_in other_addr; + socklen_t other_size; + + if((fd_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) { + perror("Error getting socket\n"); + exit(-1); + } + + my_addr.sin_family = AF_INET; + my_addr.sin_port = htons(MYPORT); /* Short, network byte order */ + my_addr.sin_addr.s_addr = INADDR_ANY; + /* Normally we'd zerofill sin_zero, but there is no such thing on Minix */ +#ifndef _MINIX + memset(my_addr.sin_zero, '\0', sizeof my_addr.sin_zero); +#endif + + /* Reuse port number. Not implemented in Minix. */ +#ifndef _MINIX + if(setsockopt(fd_sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0) { + perror("Error setting port reuse option"); + exit(-1); + } +#endif + + /* Bind to port */ + if(bind(fd_sock, (struct sockaddr *) &my_addr, sizeof my_addr) < 0) { + perror("Error binding to port"); + exit(-1); + } + + /* Mark socket to be used for incoming connections */ + if(listen(fd_sock, 20) < 0) { + perror("Listen"); + exit(-1); + } + + /* Actual testing */ + /* While sockets resemble file descriptors, they are not the same at all. + We can read/write from/to and close file descriptors, but we cannot open + them O_RDONLY or O_WRONLY; they are always O_RDWR (other flags do not make + sense regarding sockets). As such, we cannot provide wrong file descriptors + to select, except for descriptors that are not in use. + We will test standard behavior and what is described in [2]. [1] and [3] + are not possible to test on Minix, as Minix does not support OOB data. That + is, the TCP layer can handle it, but there is no socket interface for it. + Our test consists of waiting for input from the first two children and + waiting to write output [standard usage]. Then the first child closes its + connection we select for reading. This should fail with error set. Then we + close child number two on our side and select for reading. This should fail + with EBADF. Child number three shall then do a non-blocking connect (after + waiting for DO_PAUSE seconds) and do a select, resulting in being marked + ready for writing. Subsequently child number four also does a non-blocking + connect to loclhost on MYPORT+1 (causing the connect to fail) and then does + a select. This should result in write and error being set (error because of + pending error). + */ + + /* Accept and store connections from the first two children */ + other_size = sizeof(other_addr); + for(i = 0; i < 2; i++) { + fd_new = accept(fd_sock, (struct sockaddr *) &other_addr, &other_size); + if(fd_new < 0) break; + sockets[i] = fd_new; + } + + /* If we break out of the for loop, we ran across an error and want to exit. + Check whether we broke out. */ + if(fd_new < 0) { + perror("Error accepting connection"); + exit(-1); + } + + /* Select error condition checking */ + for(childresults = 0; childresults < 2; childresults++) { + FD_ZERO(&fds_read); FD_ZERO(&fds_write); FD_ZERO(&fds_error); + FD_SET(sockets[childresults], &fds_read); + FD_SET(sockets[childresults], &fds_write); + FD_SET(sockets[childresults], &fds_error); + tv.tv_sec = DO_TIMEOUT; + tv.tv_usec = 0; + + retval = select(sockets[childresults]+1, &fds_read, &fds_write, &fds_error, + &tv); + + if(retval <= 0) { + snprintf(errbuf, sizeof(errbuf), + "two fds should be set%s", (retval == 0 ? " (TIMEOUT)" : "")); + e(1, errbuf); + } + + FD_ZERO(&fds_compare_read); FD_ZERO(&fds_compare_write); + FD_SET(sockets[childresults], &fds_compare_write); + + /* We can't say much about being ready for reading at this point or not. It + is not specified and the other side might have data ready for us to read + */ + if(!compare_fds(sockets[childresults]+1, &fds_compare_write, &fds_write)) + e(2, "write should be set"); + + if(!empty_fds(sockets[childresults]+1, &fds_error)) + e(3, "no error should be set"); + } + + + /* We continue by accepting a connection of child 3 */ + fd_new = accept(fd_sock, (struct sockaddr *) &other_addr, &other_size); + if(fd_new < 0) { + perror("Error accepting connection\n"); + exit(-1); + } + sockets[2] = fd_new; + + /* Child 4 will never connect */ + + /* Child 5 is still pending to be accepted. Open Group: "If the socket is + currently listening, then it shall be marked as readable if an incoming + connection request has been received, and a call to the accept() function + shall complete without blocking."*/ + FD_ZERO(&fds_read); + FD_SET(fd_sock, &fds_read); + tv.tv_sec = DO_TIMEOUT; + tv.tv_usec = 0; + retval = select(fd_sock+1, &fds_read, NULL, NULL, &tv); + if(retval <= 0) { + snprintf(errbuf, sizeof(errbuf), + "one fd should be set%s", (retval == 0 ? " (TIMEOUT)" : "")); + e(4, errbuf); + } + + /* Check read bit is set */ + FD_ZERO(&fds_compare_read); FD_SET(fd_sock, &fds_compare_read); + if(!compare_fds(fd_sock+1, &fds_compare_read, &fds_read)) + e(5, "read should be set"); + + + /* Accept incoming connection to unblock child 5 */ + fd_new = accept(fd_sock, (struct sockaddr *) &other_addr, &other_size); + if(fd_new < 0) { + perror("Error accepting connection\n"); + exit(-1); + } + sockets[4] = fd_new; + + + /* We're done, let's wait a second to synchronize children and parent. */ + tv.tv_sec = DO_HANDLEDATA; + tv.tv_usec = 0; + select(0, NULL, NULL, NULL, &tv); + + /* Close connection with children. */ + for(i = 0; i < NUMCHILDREN; i++) { + if(i == 3) /* No need to disconnect child 4 that failed to connect. */ + continue; + + if(close(sockets[i]) < 0) { + perror(NULL); + } + } + + /* Close listening socket */ + if(close(fd_sock) < 0) { + perror("Closing listening socket"); + errct++; + } + + for(i = 0; i < NUMCHILDREN; i++) { + wait(&exitstatus); /* Wait for children */ + if(exitstatus > 0) + errct += WEXITSTATUS(exitstatus); /* and count their errors, too. */ + } + + exit(errct); +} + +int main(int argc, char **argv) { + int forkres, i; + + /* Get subtest number */ + if(argc != 2) { + printf("Usage: %s subtest_no\n", argv[0]); + exit(-2); + } else if(sscanf(argv[1], "%d", &subtest) != 1) { + printf("Usage: %s subtest_no\n", argv[0]); + exit(-2); + } + + /* Fork off a bunch of children */ + for(i = 0; i < NUMCHILDREN; i++) { + forkres = fork(); + if(forkres == 0) do_child(i); + else if(forkres < 0) { + perror("Unable to fork"); + exit(-1); + } + } + /* do_child always calls exit(), so when we end up here, we're the parent. */ + do_parent(); + + exit(-2); /* We're not supposed to get here. Both do_* routines should exit.*/ +} diff --git a/test/t40f.c b/test/t40f.c new file mode 100644 index 000000000..ccdf7d61e --- /dev/null +++ b/test/t40f.c @@ -0,0 +1,185 @@ +/* t40f.c + * + * Test timing + * + * Select works on regular files, (pseudo) terminal devices, streams-based + * files, FIFOs, pipes, and sockets. This test verifies selecting with a time + * out set. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DO_HANDLEDATA 1 +#define DO_PAUSE 3 +#define DO_TIMEOUT 7 +#define DO_DELTA 0.5 +#define MAX_ERROR 5 +#define DELTA(x,y) (x.tv_sec - y.tv_sec) * CLOCKS_PER_SEC \ + + (x.tv_usec - y.tv_usec) * CLOCKS_PER_SEC / 1000000 + +int errct = 0, subtest = -1, got_signal = 0; +int fd_ap[2]; + +void catch_signal(int sig_no) { + got_signal = 1; +} + +void e(int n, char *s) { + printf("Subtest %d, error %d, %s\n", subtest, n, s); + + if (errct++ > MAX_ERROR) { + printf("Too many errors; test aborted\n"); + exit(errct); + } +} + +float compute_diff(struct timeval start, struct timeval end, float compare) { + /* Compute time difference. It is assumed that the value of start <= end. */ + clock_t delta; + int seconds, hundreths; + float diff; + + delta = DELTA(end, start); /* delta is in ticks */ + seconds = (int) (delta / CLOCKS_PER_SEC); + hundreths = (int) (delta * 100 / CLOCKS_PER_SEC) - (seconds * 100); + + diff = seconds + (hundreths / 100.0); + diff -= compare; + if(diff < 0) diff *= -1; /* Make diff a positive value */ + + return diff; +} + +void do_child(void) { + struct timeval tv; + int retval; + + /* Let the parent do initial read and write tests from and to the pipe. */ + tv.tv_sec = DO_PAUSE + DO_PAUSE + DO_PAUSE + 1; + tv.tv_usec = 0; + retval = select(0, NULL, NULL, NULL, &tv); + + /* At this point the parent has a pending select with a DO_TIMEOUT timeout. + We're going to interrupt by sending a signal */ + if(kill(getppid(), SIGUSR1) < 0) perror("Failed to send signal"); + + exit(0); +} + +void do_parent(int child) { + fd_set fds_read; + struct timeval tv, start_time, end_time; + int retval; + + /* Install signal handler for SIGUSR1 */ + signal(SIGUSR1, catch_signal); + + /* Parent and child share an anonymous pipe. Select for read and wait for the + timeout to occur. We wait for DO_PAUSE seconds. Let's see if that's + approximately right.*/ + FD_ZERO(&fds_read); + FD_SET(fd_ap[0], &fds_read); + tv.tv_sec = DO_PAUSE; + tv.tv_usec = 0; + + (void) gettimeofday(&start_time, NULL); /* Record starting time */ + retval = select(fd_ap[0]+1, &fds_read, NULL, NULL, &tv); + (void) gettimeofday(&end_time, NULL); /* Record ending time */ + + /* Did we time out? */ + if(retval != 0) e(1, "Should have timed out"); + + /* Approximately right? The standard does not specify how precise the timeout + should be. Instead, the granularity is implementation-defined. In this + test we assume that the difference should be no more than half a second.*/ + if(compute_diff(start_time, end_time, DO_PAUSE) > DO_DELTA) + e(2, "Time difference too large"); + + /* Let's wait for another DO_PAUSE seconds, expressed as microseconds */ + FD_ZERO(&fds_read); + FD_SET(fd_ap[0], &fds_read); + tv.tv_sec = 0; + tv.tv_usec = DO_PAUSE * 1000000L; + + (void) gettimeofday(&start_time, NULL); /* Record starting time */ + retval = select(fd_ap[0]+1, &fds_read, NULL, NULL, &tv); + (void) gettimeofday(&end_time, NULL); /* Record ending time */ + + if(retval != 0) e(3, "Should have timed out"); + if(compute_diff(start_time, end_time, DO_PAUSE) > DO_DELTA) + e(4, "Time difference too large"); + + /* Let's wait for another DO_PAUSE seconds, expressed in seconds and micro + seconds. */ + FD_ZERO(&fds_read); + FD_SET(fd_ap[0], &fds_read); + tv.tv_sec = DO_PAUSE - 1; + tv.tv_usec = (DO_PAUSE - tv.tv_sec) * 1000000L; + + (void) gettimeofday(&start_time, NULL); /* Record starting time */ + retval = select(fd_ap[0]+1, &fds_read, NULL, NULL, &tv); + (void) gettimeofday(&end_time, NULL); /* Record ending time */ + + if(retval != 0) e(5, "Should have timed out"); + if(compute_diff(start_time, end_time, DO_PAUSE) > DO_DELTA) + e(6, "Time difference too large"); + + /* Finally, we test if our timeout is interrupted by a signal */ + FD_ZERO(&fds_read); + FD_SET(fd_ap[0], &fds_read); + tv.tv_sec = DO_TIMEOUT; + tv.tv_usec = 0; + + (void) gettimeofday(&start_time, NULL); /* Record starting time */ + retval = select(fd_ap[0]+1, &fds_read, NULL, NULL, &tv); + (void) gettimeofday(&end_time, NULL); /* Record ending time */ + + if(retval != -1) e(7, "Should have been interrupted"); + if(compute_diff(start_time, end_time, DO_TIMEOUT) < DO_DELTA) + e(8, "Failed to get interrupted by a signal"); + + if(!got_signal) e(9, "Failed to get interrupted by a signal"); + + waitpid(child, &retval, 0); + exit(errct); +} + +int main(int argc, char **argv) { + int forkres; + + /* Get subtest number */ + if(argc != 2) { + printf("Usage: %s subtest_no\n", argv[0]); + exit(-2); + } else if(sscanf(argv[1], "%d", &subtest) != 1) { + printf("Usage: %s subtest_no\n", argv[0]); + exit(-2); + } + + /* Set up anonymous pipe */ + if(pipe(fd_ap) < 0) { + perror("Could not create anonymous pipe"); + exit(-1); + } + + forkres = fork(); + if(forkres == 0) do_child(); + else if(forkres > 0) do_parent(forkres); + else { /* Fork failed */ + perror("Unable to fork"); + exit(-1); + } + + exit(-2); /* We're not supposed to get here. Both do_* routines should exit*/ + +} diff --git a/test/test40.c b/test/test40.c new file mode 100644 index 000000000..c763cbb00 --- /dev/null +++ b/test/test40.c @@ -0,0 +1,53 @@ +/* Test40.c + * + * Test select(...) system call + * + */ + +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) { + char *tests[] = {"t40a", "t40b", "t40c", "t40d", "t40e", "t40f"}; + int no_tests, i, forkres, status = 0, errorct = 0; + + no_tests = sizeof(tests) / sizeof(char *); + + printf("Test 40 "); + fflush(stdout); + + for(i = 0; i < no_tests; i++) { + char subtest[2]; + sprintf(subtest, "%d", i+1); + + forkres = fork(); + if(forkres == 0) { /* Child */ + execl(tests[i], tests[i], subtest, (char *) 0); + printf("Failed to execute subtest %s\n", tests[i]); + exit(-2); + } else if(forkres > 0) { /* Parent */ + if(waitpid(forkres, &status, 0) > 0 && WEXITSTATUS(status) < 20) { + errorct += WEXITSTATUS(status); /* Count errors */ + } + status = 0; /* Reset */ + } else { + printf("Failed to fork\n"); + exit(-2); + } + } + + if(errorct == 0) { + printf("Ok\n"); + exit(0); + } else { + printf("%d error(s)\n", errorct); + exit(1); + } + + return (-1); /* Impossible */ +} +