 5977114c42
			
		
	
	
		5977114c42
		
	
	
	
	
		
			
			. make common.o link with the tests instead of being #included as common.c . fix warnings about missing prototypes by declaring functions static . reduces some duplicated code Change-Id: Ic2a765d7f5886add5863190efec3fdd2d2ea2137
		
			
				
	
	
		
			429 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			429 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* 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>
 | |
| 
 | |
| #include "common.h"
 | |
| 
 | |
| #define DO_HANDLEDATA 1
 | |
| #define DO_PAUSE 3
 | |
| #define DO_TIMEOUT 7
 | |
| #define MYPORT 3490
 | |
| #define NUMCHILDREN 5
 | |
| #define MAX_ERROR 10
 | |
| 
 | |
| char errbuf[1000];
 | |
| 
 | |
| /* All *_fds routines are helping routines. They intentionally use FD_* macros
 | |
|    in order to prevent making assumptions on how the macros are implemented.*/
 | |
| 
 | |
| #if 0
 | |
| static 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;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| static 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;
 | |
| }
 | |
| 
 | |
| static 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;
 | |
| }
 | |
| 
 | |
| #if 0
 | |
| static 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");
 | |
|   }
 | |
| }
 | |
| #endif
 | |
| 
 | |
| static 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 */
 | |
| #if !defined(__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) em(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))
 | |
|       em(7, "write should be set");
 | |
|   }
 | |
| 
 | |
|   if(close(fd_sock) < 0) {
 | |
|     perror("Error disconnecting");
 | |
|     exit(-1);
 | |
|   }
 | |
| 
 | |
|   exit(errct);
 | |
| }
 | |
| 
 | |
| static void do_parent(void) {
 | |
| #if !defined(__minix)
 | |
|   int yes = 1;
 | |
| #endif
 | |
|   int fd_sock, fd_new, 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 */
 | |
| #if !defined(__minix)
 | |
|   memset(my_addr.sin_zero, '\0', sizeof my_addr.sin_zero);
 | |
| #endif
 | |
|   
 | |
|   /* Reuse port number. Not implemented in Minix. */
 | |
| #if !defined(__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)" : ""));
 | |
|       em(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))
 | |
|       em(2, "write should be set");
 | |
| 
 | |
|     if(!empty_fds(sockets[childresults]+1, &fds_error))
 | |
|       em(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)" : ""));
 | |
|     em(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))
 | |
|     em(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.*/
 | |
| }
 |