minix3/tests/t40e.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.*/
}