]> Zhao Yanbai Git Server - minix.git/commitdiff
Added select test
authorThomas Veerman <thomas@minix3.org>
Tue, 14 Jul 2009 09:43:33 +0000 (09:43 +0000)
committerThomas Veerman <thomas@minix3.org>
Tue, 14 Jul 2009 09:43:33 +0000 (09:43 +0000)
test/Makefile
test/run
test/t40a.c [new file with mode: 0644]
test/t40b.c [new file with mode: 0644]
test/t40c.c [new file with mode: 0644]
test/t40d.c [new file with mode: 0644]
test/t40e.c [new file with mode: 0644]
test/t40f.c [new file with mode: 0644]
test/test40.c [new file with mode: 0644]

index af1b8d98aa1bd36e89631bf8f3bd60bd7051c2f0..5f788ced7c815cf6339d39a225da4534bbb09540 100644 (file)
@@ -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
index aee52bbac9aa056f33154601e5b607eb7c2d0bba..30ca39b5de7afff314f4447ceae9b83cee47e719 100755 (executable)
--- 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 (file)
index 0000000..8dcbc7b
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/select.h>
+#include <errno.h>
+#include <limits.h>
+
+#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 (file)
index 0000000..a23abaa
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/select.h>
+#include <errno.h>
+#include <time.h>
+
+#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 (file)
index 0000000..c516229
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/select.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <string.h>
+
+#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 (file)
index 0000000..ffda1bb
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/select.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+
+#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 (file)
index 0000000..5a5ba4c
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+#include <netdb.h>
+
+#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 (file)
index 0000000..ccdf7d6
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/select.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <time.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+
+#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 (file)
index 0000000..c763cbb
--- /dev/null
@@ -0,0 +1,53 @@
+/* Test40.c
+ *
+ * Test select(...) system call
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+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 */
+}
+