1024 lines
24 KiB
C
1024 lines
24 KiB
C
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#include <sys/param.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/ioctl.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "common.h"
|
|
#include "socklib.h"
|
|
|
|
/* 0 = check, 1 = generate source, 2 = generate CSV */
|
|
#define SOCKLIB_SWEEP_GENERATE 0
|
|
|
|
#if SOCKLIB_SWEEP_GENERATE
|
|
/* Link against minix/usr.bin/trace/error.o to make this work! */
|
|
const char *get_error_name(int err);
|
|
|
|
#if SOCKLIB_SWEEP_GENERATE == 2
|
|
static const char *statename[S_MAX] = {
|
|
"S_NEW",
|
|
"S_N_SHUT_R",
|
|
"S_N_SHUT_W",
|
|
"S_N_SHUT_RW",
|
|
"S_BOUND",
|
|
"S_LISTENING",
|
|
"S_L_SHUT_R",
|
|
"S_L_SHUT_W",
|
|
"S_L_SHUT_RW",
|
|
"S_CONNECTING",
|
|
"S_C_SHUT_R",
|
|
"S_C_SHUT_W",
|
|
"S_C_SHUT_RW",
|
|
"S_CONNECTED",
|
|
"S_ACCEPTED",
|
|
"S_SHUT_R",
|
|
"S_SHUT_W",
|
|
"S_SHUT_RW",
|
|
"S_RSHUT_R",
|
|
"S_RSHUT_W",
|
|
"S_RSHUT_RW",
|
|
"S_SHUT2_R",
|
|
"S_SHUT2_W",
|
|
"S_SHUT2_RW",
|
|
"S_PRE_EOF",
|
|
"S_AT_EOF",
|
|
"S_POST_EOF",
|
|
"S_PRE_SHUT_R",
|
|
"S_EOF_SHUT_R",
|
|
"S_POST_SHUT_R",
|
|
"S_PRE_SHUT_W",
|
|
"S_EOF_SHUT_W",
|
|
"S_POST_SHUT_W",
|
|
"S_PRE_SHUT_RW",
|
|
"S_EOF_SHUT_RW",
|
|
"S_POST_SHUT_RW",
|
|
"S_PRE_RESET",
|
|
"S_AT_RESET",
|
|
"S_POST_RESET",
|
|
"S_FAILED",
|
|
"S_POST_FAILED",
|
|
};
|
|
#endif
|
|
|
|
static const char *callname[C_MAX] = {
|
|
"C_ACCEPT",
|
|
"C_BIND",
|
|
"C_CONNECT",
|
|
"C_GETPEERNAME",
|
|
"C_GETSOCKNAME",
|
|
"C_GETSOCKOPT_ERR",
|
|
"C_GETSOCKOPT_KA",
|
|
"C_GETSOCKOPT_RB",
|
|
"C_IOCTL_NREAD",
|
|
"C_LISTEN",
|
|
"C_RECV",
|
|
"C_RECVFROM",
|
|
"C_SEND",
|
|
"C_SENDTO",
|
|
"C_SELECT_R",
|
|
"C_SELECT_W",
|
|
"C_SELECT_X",
|
|
"C_SETSOCKOPT_BC",
|
|
"C_SETSOCKOPT_KA",
|
|
"C_SETSOCKOPT_L",
|
|
"C_SETSOCKOPT_RA",
|
|
"C_SHUTDOWN_R",
|
|
"C_SHUTDOWN_RW",
|
|
"C_SHUTDOWN_W",
|
|
};
|
|
#endif
|
|
|
|
static int socklib_sigpipe;
|
|
|
|
/*
|
|
* Signal handler for SIGPIPE signals.
|
|
*/
|
|
static void
|
|
socklib_signal(int sig)
|
|
{
|
|
|
|
if (sig != SIGPIPE) e(0);
|
|
|
|
socklib_sigpipe++;
|
|
}
|
|
|
|
/*
|
|
* The given socket file descriptor 'fd' has been set up in the desired state.
|
|
* Perform the given call 'call' on it, possibly using local socket address
|
|
* 'local_addr' (for binding) or remote socket address 'remote_addr' (for
|
|
* connecting or to store resulting addresses), both of size 'addr_len'.
|
|
* Return the result of the call, using a positive value if the call succeeded,
|
|
* or a negated errno code if the call failed.
|
|
*/
|
|
int
|
|
socklib_sweep_call(enum call call, int fd, struct sockaddr * local_addr,
|
|
struct sockaddr * remote_addr, socklen_t addr_len)
|
|
{
|
|
char data[1];
|
|
struct linger l;
|
|
fd_set fd_set;
|
|
struct timeval tv;
|
|
socklen_t len;
|
|
int i, r, fd2;
|
|
|
|
fd2 = -1;
|
|
|
|
switch (call) {
|
|
case C_ACCEPT:
|
|
r = accept(fd, remote_addr, &addr_len);
|
|
|
|
if (r >= 0)
|
|
fd2 = r;
|
|
|
|
break;
|
|
|
|
case C_BIND:
|
|
r = bind(fd, local_addr, addr_len);
|
|
|
|
break;
|
|
|
|
case C_CONNECT:
|
|
r = connect(fd, remote_addr, addr_len);
|
|
|
|
break;
|
|
|
|
case C_GETPEERNAME:
|
|
r = getpeername(fd, remote_addr, &addr_len);
|
|
|
|
break;
|
|
|
|
case C_GETSOCKNAME:
|
|
r = getsockname(fd, remote_addr, &addr_len);
|
|
|
|
break;
|
|
|
|
case C_GETSOCKOPT_ERR:
|
|
len = sizeof(i);
|
|
|
|
r = getsockopt(fd, SOL_SOCKET, SO_ERROR, &i, &len);
|
|
|
|
/*
|
|
* We assume this call always succeeds, and test against the
|
|
* pending error.
|
|
*/
|
|
if (r != 0) e(0);
|
|
if (i != 0) {
|
|
r = -1;
|
|
errno = i;
|
|
}
|
|
|
|
break;
|
|
|
|
case C_GETSOCKOPT_KA:
|
|
len = sizeof(i);
|
|
|
|
r = getsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &i, &len);
|
|
|
|
break;
|
|
|
|
case C_GETSOCKOPT_RB:
|
|
len = sizeof(i);
|
|
|
|
r = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &i, &len);
|
|
|
|
break;
|
|
|
|
case C_IOCTL_NREAD:
|
|
r = ioctl(fd, FIONREAD, &i);
|
|
|
|
/* On success, we test against the returned value here. */
|
|
if (r == 0)
|
|
r = i;
|
|
|
|
break;
|
|
|
|
case C_LISTEN:
|
|
r = listen(fd, 1);
|
|
|
|
break;
|
|
|
|
case C_RECV:
|
|
r = recv(fd, data, sizeof(data), 0);
|
|
|
|
break;
|
|
|
|
case C_RECVFROM:
|
|
r = recvfrom(fd, data, sizeof(data), 0, remote_addr,
|
|
&addr_len);
|
|
|
|
break;
|
|
|
|
case C_SEND:
|
|
data[0] = 0;
|
|
|
|
r = send(fd, data, sizeof(data), 0);
|
|
|
|
break;
|
|
|
|
case C_SENDTO:
|
|
data[0] = 0;
|
|
|
|
r = sendto(fd, data, sizeof(data), 0, remote_addr, addr_len);
|
|
|
|
break;
|
|
|
|
case C_SETSOCKOPT_BC:
|
|
i = 0;
|
|
|
|
r = setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &i, sizeof(i));
|
|
|
|
break;
|
|
|
|
case C_SETSOCKOPT_KA:
|
|
i = 1;
|
|
|
|
r = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i));
|
|
|
|
break;
|
|
|
|
case C_SETSOCKOPT_L:
|
|
l.l_onoff = 1;
|
|
l.l_linger = 0;
|
|
|
|
r = setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l));
|
|
|
|
break;
|
|
|
|
case C_SETSOCKOPT_RA:
|
|
i = 1;
|
|
|
|
r = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
|
|
|
|
break;
|
|
|
|
case C_SELECT_R:
|
|
case C_SELECT_W:
|
|
case C_SELECT_X:
|
|
FD_ZERO(&fd_set);
|
|
FD_SET(fd, &fd_set);
|
|
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 0;
|
|
|
|
r = select(fd + 1, (call == C_SELECT_R) ? &fd_set : NULL,
|
|
(call == C_SELECT_W) ? &fd_set : NULL,
|
|
(call == C_SELECT_X) ? &fd_set : NULL, &tv);
|
|
|
|
break;
|
|
|
|
case C_SHUTDOWN_R:
|
|
r = shutdown(fd, SHUT_RD);
|
|
|
|
break;
|
|
|
|
case C_SHUTDOWN_W:
|
|
r = shutdown(fd, SHUT_WR);
|
|
|
|
break;
|
|
|
|
case C_SHUTDOWN_RW:
|
|
r = shutdown(fd, SHUT_RDWR);
|
|
|
|
break;
|
|
|
|
default:
|
|
r = -1;
|
|
errno = EINVAL;
|
|
e(0);
|
|
}
|
|
|
|
if (r < -1) e(0);
|
|
|
|
if (r == -1)
|
|
r = -errno;
|
|
|
|
if (fd2 >= 0 && close(fd2) != 0) e(0);
|
|
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* Perform a sweep of socket calls vs socket states, testing the outcomes
|
|
* against provided tables or (if SOCKLIB_SWEEP_GENERATE is set) reporting on
|
|
* the outcomes instead. The caller must provide the following:
|
|
*
|
|
* - the socket domain, type, and protocol to test; these are simply forwarded
|
|
* to the callback function (see below);
|
|
* - the set of S_ states to test, as array 'states' with 'nstates' elements;
|
|
* - unless generating output, a matrix of expected results as 'results', which
|
|
* is actually a two-dimensional array with dimensions [C_MAX][nstates], with
|
|
* either positive call output or a negated call errno code in each cell;
|
|
* - a callback function 'proc' that must set up a socket in the given state
|
|
* and pass it to socklib_sweep_call().
|
|
*
|
|
* The 'states' array allows each socket sweep test to support a different set
|
|
* of states, because not every type of socket can be put in every possible
|
|
* state. All calls are always tried in each state, though.
|
|
*
|
|
* The sweep also tests for SIGPIPE generation, which assumes that all calls on
|
|
* SOCK_STREAM sockets that return EPIPE, also raise a SIGPIPE signal, and that
|
|
* no other SIGPIPE signal is ever raised otherwise.
|
|
*
|
|
* Standard e() error throwing is used for set-up and result mismatches.
|
|
*/
|
|
void
|
|
socklib_sweep(int domain, int type, int protocol, const enum state * states,
|
|
unsigned int nstates, const int * results, int (* proc)(int domain,
|
|
int type, int protocol, enum state, enum call))
|
|
{
|
|
struct sigaction act, oact;
|
|
enum state state;
|
|
enum call call;
|
|
#if SOCKLIB_SWEEP_GENERATE
|
|
const char *name;
|
|
int res, *nresults;
|
|
#else
|
|
int res, exp;
|
|
#endif
|
|
|
|
memset(&act, 0, sizeof(act));
|
|
act.sa_handler = socklib_signal;
|
|
if (sigaction(SIGPIPE, &act, &oact) != 0) e(0);
|
|
|
|
#if SOCKLIB_SWEEP_GENERATE
|
|
if ((nresults = malloc(nstates * C_MAX)) == NULL) e(0);
|
|
#endif
|
|
|
|
for (state = 0; state < nstates; state++) {
|
|
for (call = 0; call < C_MAX; call++) {
|
|
socklib_sigpipe = 0;
|
|
|
|
res = proc(domain, type, protocol, states[state],
|
|
call);
|
|
|
|
/*
|
|
* If the result was EPIPE and this is a stream-type
|
|
* socket, we must have received exactly one SIGPIPE
|
|
* signal. Otherwise, we must not have received one.
|
|
* Note that technically, the SIGPIPE could arrive
|
|
* sometime after this check, but with regular system
|
|
* service scheduling that will never happen.
|
|
*/
|
|
if (socklib_sigpipe !=
|
|
(res == -EPIPE && type == SOCK_STREAM)) e(0);
|
|
|
|
#if SOCKLIB_SWEEP_GENERATE
|
|
nresults[call * nstates + state] = res;
|
|
#else
|
|
exp = results[call * nstates + state];
|
|
|
|
if (res != exp) {
|
|
printf("FAIL state %d call %d res %d exp %d\n",
|
|
state, call, res, exp);
|
|
e(0);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (sigaction(SIGPIPE, &oact, NULL) != 0) e(0);
|
|
|
|
#if SOCKLIB_SWEEP_GENERATE
|
|
#if SOCKLIB_SWEEP_GENERATE == 1
|
|
/*
|
|
* Generate a table in C form, ready to be pasted into test source.
|
|
* Obviously, generated results should be hand-checked carefully before
|
|
* being pasted into a test. Arguably these tables should be hand-made
|
|
* for maximum scrutiny, but I already checked the results from the
|
|
* CSV form (#define SOCKLIB_SWEEP_GENERATE 2) and have no desire for
|
|
* RSI -dcvmoole
|
|
*/
|
|
printf("\nstatic const int X_results[][__arraycount(X_states)] = {\n");
|
|
for (call = 0; call < C_MAX; call++) {
|
|
if ((name = callname[call]) == NULL) e(0);
|
|
printf("\t[%s]%s%s%s= {", name,
|
|
(strlen(name) <= 21) ? "\t" : "",
|
|
(strlen(name) <= 13) ? "\t" : "",
|
|
(strlen(name) <= 5) ? "\t" : "");
|
|
for (state = 0; state < nstates; state++) {
|
|
if (state % 4 == 0)
|
|
printf("\n\t\t");
|
|
res = nresults[call * nstates + state];
|
|
name = (res < 0) ? get_error_name(-res) : NULL;
|
|
if (name != NULL) {
|
|
printf("-%s,", name);
|
|
if ((state + 1) % 4 != 0 &&
|
|
state < nstates - 1)
|
|
printf("%s%s",
|
|
(strlen(name) <= 13) ? "\t" : "",
|
|
(strlen(name) <= 5) ? "\t" : "");
|
|
} else {
|
|
printf("%d,", res);
|
|
if ((state + 1) % 4 != 0 &&
|
|
state < nstates - 1)
|
|
printf("\t\t");
|
|
}
|
|
}
|
|
printf("\n\t},\n");
|
|
}
|
|
printf("};\n");
|
|
#elif SOCKLIB_SWEEP_GENERATE == 2
|
|
/* Generate table in CSV form. */
|
|
printf("\n");
|
|
for (state = 0; state < nstates; state++)
|
|
printf(",%s", statename[states[state]] + 2);
|
|
for (call = 0; call < C_MAX; call++) {
|
|
printf("\n%s", callname[call] + 2);
|
|
for (state = 0; state < nstates; state++) {
|
|
res = nresults[call * nstates + state];
|
|
name = (res < 0) ? get_error_name(-res) : NULL;
|
|
if (name != NULL)
|
|
printf(",%s", name);
|
|
else
|
|
printf(",%d", res);
|
|
}
|
|
}
|
|
printf("\n");
|
|
#endif
|
|
|
|
free(nresults);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Test for large sends and receives on stream sockets with MSG_WAITALL.
|
|
*/
|
|
void
|
|
socklib_large_transfers(int fd[2])
|
|
{
|
|
char *buf;
|
|
pid_t pid;
|
|
int i, status;
|
|
|
|
#define LARGE_BUF (4096*1024)
|
|
|
|
if ((buf = malloc(LARGE_BUF)) == NULL) e(0);
|
|
memset(buf, 0, LARGE_BUF);
|
|
|
|
pid = fork();
|
|
switch (pid) {
|
|
case 0:
|
|
errct = 0;
|
|
|
|
if (close(fd[0]) != 0) e(0);
|
|
|
|
/* Part 1. */
|
|
if (recv(fd[1], buf, LARGE_BUF, MSG_WAITALL) != LARGE_BUF)
|
|
e(0);
|
|
|
|
for (i = 0; i < LARGE_BUF; i++)
|
|
if (buf[i] != (char)(i + (i >> 16))) e(0);
|
|
|
|
if (recv(fd[1], buf, LARGE_BUF,
|
|
MSG_DONTWAIT | MSG_WAITALL) != -1) e(0);
|
|
if (errno != EWOULDBLOCK) e(0);
|
|
|
|
/* Part 2. */
|
|
if (send(fd[1], buf, LARGE_BUF / 2, 0) != LARGE_BUF / 2) e(0);
|
|
|
|
if (shutdown(fd[1], SHUT_WR) != 0) e(0);
|
|
|
|
/* Part 3. */
|
|
memset(buf, 'y', LARGE_BUF);
|
|
|
|
if (recv(fd[1], buf, LARGE_BUF, MSG_WAITALL) != LARGE_BUF - 1)
|
|
e(0);
|
|
|
|
for (i = 0; i < LARGE_BUF - 1; i++)
|
|
if (buf[i] != (char)(i + (i >> 16))) e(0);
|
|
if (buf[LARGE_BUF - 1] != 'y') e(0);
|
|
|
|
if (recv(fd[1], buf, LARGE_BUF, MSG_WAITALL) != 0) e(0);
|
|
|
|
exit(errct);
|
|
case -1:
|
|
e(0);
|
|
}
|
|
|
|
if (close(fd[1]) != 0) e(0);
|
|
|
|
/* Part 1: check that a large send fully arrives. */
|
|
for (i = 0; i < LARGE_BUF; i++)
|
|
buf[i] = (char)(i + (i >> 16));
|
|
|
|
if (send(fd[0], buf, LARGE_BUF, 0) != LARGE_BUF) e(0);
|
|
|
|
/* Part 2: check that remote shutdown terminates a partial receive. */
|
|
memset(buf, 'x', LARGE_BUF);
|
|
|
|
if (recv(fd[0], buf, LARGE_BUF, MSG_WAITALL) != LARGE_BUF / 2) e(0);
|
|
|
|
for (i = 0; i < LARGE_BUF / 2; i++)
|
|
if (buf[i] != (char)(i + (i >> 16))) e(0);
|
|
for (; i < LARGE_BUF; i++)
|
|
if (buf[i] != 'x') e(0);
|
|
|
|
if (recv(fd[0], buf, LARGE_BUF, MSG_WAITALL) != 0) e(0);
|
|
|
|
/* Part 3: check that remote close terminates a partial receive. */
|
|
for (i = 0; i < LARGE_BUF; i++)
|
|
buf[i] = (char)(i + (i >> 16));
|
|
|
|
if (send(fd[0], buf, LARGE_BUF - 1, 0) != LARGE_BUF - 1) e(0);
|
|
|
|
if (close(fd[0]) != 0) e(0);
|
|
|
|
if (waitpid(pid, &status, 0) != pid) e(0);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
|
|
|
|
free(buf);
|
|
}
|
|
|
|
#define PRINT_STATS 0
|
|
|
|
/*
|
|
* A randomized producer-consumer test for stream sockets. As part of this,
|
|
* we also perform very basic bulk functionality tests of FIONREAD, MSG_PEEK,
|
|
* MSG_DONTWAIT, and MSG_WAITALL.
|
|
*/
|
|
void
|
|
socklib_producer_consumer(int fd[2])
|
|
{
|
|
char *buf;
|
|
time_t t;
|
|
socklen_t len, size, off;
|
|
ssize_t r;
|
|
pid_t pid;
|
|
int i, rcvlen, status, exp, flags, num, stat[3] = { 0, 0, 0 };
|
|
|
|
len = sizeof(rcvlen);
|
|
if (getsockopt(fd[0], SOL_SOCKET, SO_RCVBUF, &rcvlen, &len) != 0) e(0);
|
|
if (len != sizeof(rcvlen)) e(0);
|
|
|
|
size = rcvlen * 3;
|
|
|
|
if ((buf = malloc(size)) == NULL) e(0);
|
|
|
|
t = time(NULL);
|
|
|
|
/*
|
|
* We vary small versus large (random) send and receive sizes,
|
|
* splitting the entire transfer in four phases along those lines.
|
|
*
|
|
* In theory, the use of an extra system call, the use of MSG_PEEK, and
|
|
* the fact that without MSG_WAITALL a receive call may return any
|
|
* partial result, all contribute to the expectation that the consumer
|
|
* side will fall behind the producer. In order to test both filling
|
|
* and draining the receive queue, we use a somewhat larger small
|
|
* receive size for the consumer size (up to 256 bytes rather than 64)
|
|
* during each half of the four phases. The effectiveness of these
|
|
* numbers can be verified with statistics (disabled by default).
|
|
*/
|
|
#define TRANSFER_SIZE (16 * 1024 * 1024)
|
|
|
|
pid = fork();
|
|
switch (pid) {
|
|
case 0:
|
|
errct = 0;
|
|
|
|
if (close(fd[0]) != 0) e(0);
|
|
|
|
srand48(t + 1);
|
|
|
|
for (off = 0; off < TRANSFER_SIZE; ) {
|
|
if (off < TRANSFER_SIZE / 2)
|
|
len = lrand48() %
|
|
((off / (TRANSFER_SIZE / 8) % 2) ? 64 :
|
|
256);
|
|
else
|
|
len = lrand48() % size;
|
|
|
|
num = lrand48() % 16;
|
|
flags = 0;
|
|
if (num & 1) flags |= MSG_PEEK;
|
|
if (num & 2) flags |= MSG_WAITALL;
|
|
if (num & 4) flags |= MSG_DONTWAIT;
|
|
if (num & 8) {
|
|
/*
|
|
* Obviously there are race conditions here but
|
|
* the returned number should be a lower bound.
|
|
*/
|
|
if (ioctl(fd[1], FIONREAD, &exp) != 0) e(0);
|
|
if (exp < 0 || exp > rcvlen) e(0);
|
|
} else
|
|
exp = -1;
|
|
|
|
stat[0]++;
|
|
|
|
if ((r = recv(fd[1], buf, len, flags)) == -1) {
|
|
if (errno != EWOULDBLOCK) e(0);
|
|
if (exp > 0) e(0);
|
|
|
|
stat[2]++;
|
|
continue;
|
|
}
|
|
|
|
if (r < len) {
|
|
stat[1]++;
|
|
|
|
if (exp > r) e(0);
|
|
}
|
|
|
|
for (i = 0; i < r; i++)
|
|
if (buf[i] != (char)((off + i) +
|
|
((off + i) >> 16))) e(0);
|
|
|
|
if (!(flags & MSG_PEEK)) {
|
|
off += r;
|
|
|
|
if ((flags & (MSG_DONTWAIT | MSG_WAITALL)) ==
|
|
MSG_WAITALL && r != len &&
|
|
off < TRANSFER_SIZE) e(0);
|
|
}
|
|
}
|
|
|
|
#if PRINT_STATS
|
|
/*
|
|
* The second and third numbers should ideally be a large but
|
|
* non-dominating fraction of the first one.
|
|
*/
|
|
printf("RECV: total %d short %d again %d\n",
|
|
stat[0], stat[1], stat[2]);
|
|
#endif
|
|
|
|
if (close(fd[1]) != 0) e(0);
|
|
exit(errct);
|
|
case -1:
|
|
e(0);
|
|
}
|
|
|
|
if (close(fd[1]) != 0) e(0);
|
|
|
|
srand48(t);
|
|
|
|
for (off = 0; off < TRANSFER_SIZE; ) {
|
|
if (off < TRANSFER_SIZE / 4 ||
|
|
(off >= TRANSFER_SIZE / 2 && off < TRANSFER_SIZE * 3 / 4))
|
|
len = lrand48() % 64;
|
|
else
|
|
len = lrand48() % size;
|
|
|
|
if (len > TRANSFER_SIZE - off)
|
|
len = TRANSFER_SIZE - off;
|
|
|
|
for (i = 0; i < len; i++)
|
|
buf[i] = (off + i) + ((off + i) >> 16);
|
|
|
|
flags = (lrand48() % 2) ? MSG_DONTWAIT : 0;
|
|
|
|
stat[0]++;
|
|
|
|
r = send(fd[0], buf, len, flags);
|
|
|
|
if (r != len) {
|
|
if (r > (ssize_t)len) e(0);
|
|
if (!(flags & MSG_DONTWAIT)) e(0);
|
|
if (r == -1) {
|
|
if (errno != EWOULDBLOCK) e(0);
|
|
r = 0;
|
|
|
|
stat[2]++;
|
|
} else
|
|
stat[1]++;
|
|
}
|
|
|
|
if (off / (TRANSFER_SIZE / 4) !=
|
|
(off + r) / (TRANSFER_SIZE / 4))
|
|
sleep(1);
|
|
|
|
off += r;
|
|
}
|
|
|
|
#if PRINT_STATS
|
|
/*
|
|
* The second and third numbers should ideally be a large but non-
|
|
* dominating fraction of the first one.
|
|
*/
|
|
printf("SEND: total %d short %d again %d\n",
|
|
stat[0], stat[1], stat[2]);
|
|
#endif
|
|
|
|
free(buf);
|
|
|
|
if (close(fd[0]) != 0) e(0);
|
|
|
|
if (waitpid(pid, &status, 0) != pid) e(0);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
|
|
}
|
|
|
|
/*
|
|
* Signal handler which just needs to exist, so that invoking it will interrupt
|
|
* an ongoing system call.
|
|
*/
|
|
static void
|
|
socklib_got_signal(int sig __unused)
|
|
{
|
|
|
|
/* Nothing. */
|
|
}
|
|
|
|
/*
|
|
* Test for receiving on stream sockets. The quick summary here is that
|
|
* recv(MSG_WAITALL) should keep suspending until as many bytes as requested
|
|
* are also received (or the call is interrupted, or no more can possibly be
|
|
* received - the meaning of the latter depends on the domain), and,
|
|
* SO_RCVLOWAT acts as an admission test for the receive: nothing is received
|
|
* until there are at least as many bytes are available in the receive buffer
|
|
* as the low receive watermark, or the whole receive request length, whichever
|
|
* is smaller. In addition, select(2) should use the same threshold.
|
|
*/
|
|
#define MAX_BYTES 2 /* set to 3 for slightly better(?) testing */
|
|
#define USLEEP_TIME 250000 /* increase on wimpy platforms if needed */
|
|
|
|
static void
|
|
socklib_stream_recv_sub(int (* socket_pair)(int, int, int, int *), int domain,
|
|
int type, int idata, int istate, int rlowat, int len, int bits,
|
|
int act, int (* break_recv)(int, const char *, size_t))
|
|
{
|
|
const char *data = "ABCDE"; /* this limits MAX_BYTES to 3 */
|
|
struct sigaction sa;
|
|
struct timeval tv;
|
|
fd_set fds;
|
|
char buf[3];
|
|
pid_t pid;
|
|
int fd[2], val, flags, min, res, err;
|
|
int pfd[2], edata, tstate, fl, status;
|
|
|
|
if (socket_pair(domain, type, 0, fd) != 0) e(0);
|
|
|
|
/*
|
|
* Set up the initial condition on the sockets.
|
|
*/
|
|
if (idata > 0)
|
|
if (send(fd[1], data, idata, 0) != idata) e(0);
|
|
|
|
switch (istate) {
|
|
case 0: break;
|
|
case 1: if (shutdown(fd[0], SHUT_RD) != 0) e(0); break;
|
|
case 2: if (shutdown(fd[1], SHUT_WR) != 0) e(0); break;
|
|
case 3: if (close(fd[1]) != 0) e(0); break;
|
|
}
|
|
|
|
/* Set the low receive water mark. */
|
|
if (setsockopt(fd[0], SOL_SOCKET, SO_RCVLOWAT, &rlowat,
|
|
sizeof(rlowat)) != 0) e(0);
|
|
|
|
/* SO_RCVLOWAT is always bounded by the actual receive length. */
|
|
min = MIN(len, rlowat);
|
|
|
|
/*
|
|
* Do a quick select test to see if its result indeed matches whether
|
|
* the available data in the receive buffer meets the threshold.
|
|
*/
|
|
FD_ZERO(&fds);
|
|
FD_SET(fd[0], &fds);
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 0;
|
|
res = select(fd[0] + 1, &fds, NULL, NULL, &tv);
|
|
if (res < 0 || res > 1) e(0);
|
|
if (res != (idata >= rlowat || istate > 0)) e(0);
|
|
if (res == 1 && !FD_ISSET(fd[0], &fds)) e(0);
|
|
|
|
/* Also do a quick test for ioctl(FIONREAD). */
|
|
if (ioctl(fd[0], FIONREAD, &val) != 0) e(0);
|
|
if (val != ((istate != 1) ? idata : 0)) e(0);
|
|
|
|
/* Translate the given bits to receive call flags. */
|
|
flags = 0;
|
|
if (bits & 1) flags |= MSG_PEEK;
|
|
if (bits & 2) flags |= MSG_DONTWAIT;
|
|
if (bits & 4) flags |= MSG_WAITALL;
|
|
|
|
/*
|
|
* Cut short a whole lot of cases, to avoid the overhead of forking,
|
|
* namely when we know the call should return immediately. This is
|
|
* the case when MSG_DONTWAIT is set, or if a termination condition has
|
|
* been raised, or if enough initial data are available to meet the
|
|
* conditions for the receive call.
|
|
*/
|
|
if ((flags & MSG_DONTWAIT) || istate > 0 || (idata >= min &&
|
|
((flags & (MSG_PEEK | MSG_WAITALL)) != MSG_WAITALL ||
|
|
idata >= len))) {
|
|
res = recv(fd[0], buf, len, flags);
|
|
|
|
if (res == -1 && errno != EWOULDBLOCK) e(0);
|
|
|
|
/*
|
|
* If the socket has been shutdown locally, we will never get
|
|
* anything but zero. Otherwise, if we meet the SO_RCVLOWAT
|
|
* test, we should have received as much as was available and
|
|
* requested. Otherwise, if the remote end has been shut down
|
|
* or closed, we expected to get any available data or
|
|
* otherwise EOF (implied with idata==0). If none of these
|
|
* cases apply, we should have gotten EWOULDBLOCK.
|
|
*/
|
|
if (istate == 1) {
|
|
if (res != 0) e(0);
|
|
} else if (idata >= min) {
|
|
if (res != MIN(len, idata)) e(0);
|
|
if (strncmp(buf, data, res)) e(0);
|
|
} else if (istate > 0) {
|
|
if (res != idata) e(0);
|
|
if (strncmp(buf, data, res)) e(0);
|
|
} else
|
|
if (res != -1) e(0);
|
|
|
|
/* Early cleanup and return to avoid even more code clutter. */
|
|
if (istate != 3 && close(fd[1]) != 0) e(0);
|
|
if (close(fd[0]) != 0) e(0);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Now starts the interesting stuff: the receive call should now block,
|
|
* even though if we add MSG_DONTWAIT it may not return EWOULDBLOCK,
|
|
* because MSG_DONTWAIT overrides MSG_WAITALL. As such, we can only
|
|
* test our expectations by actually letting the call block, in a child
|
|
* process, and waiting. We do test as much of the above assumption as
|
|
* we can just for safety right here, but this is not a substitute for
|
|
* actually blocking even in these cases!
|
|
*/
|
|
if (!(flags & MSG_WAITALL)) {
|
|
if (recv(fd[0], buf, len, flags | MSG_DONTWAIT) != -1) e(0);
|
|
if (errno != EWOULDBLOCK) e(0);
|
|
}
|
|
|
|
/*
|
|
* If (act < 12), we send 0, 1, or 2 extra data bytes before forcing
|
|
* the receive call to terminate in one of four ways.
|
|
*
|
|
* If (act == 12), we use a signal to interrupt the receive call.
|
|
*/
|
|
if (act < 12) {
|
|
edata = act % 3;
|
|
tstate = act / 3;
|
|
} else
|
|
edata = tstate = 0;
|
|
|
|
if (pipe2(pfd, O_NONBLOCK) != 0) e(0);
|
|
|
|
pid = fork();
|
|
switch (pid) {
|
|
case 0:
|
|
errct = 0;
|
|
|
|
if (close(fd[1]) != 0) e(0);
|
|
if (close(pfd[0]) != 0) e(0);
|
|
|
|
if (act == 12) {
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sa_handler = socklib_got_signal;
|
|
if (sigaction(SIGUSR1, &sa, NULL) != 0) e(0);
|
|
}
|
|
|
|
res = recv(fd[0], buf, len, flags);
|
|
err = errno;
|
|
|
|
if (write(pfd[1], &res, sizeof(res)) != sizeof(res)) e(0);
|
|
if (write(pfd[1], &err, sizeof(err)) != sizeof(err)) e(0);
|
|
|
|
if (res > 0 && strncmp(buf, data, res)) e(0);
|
|
|
|
exit(errct);
|
|
case -1:
|
|
e(0);
|
|
}
|
|
|
|
if (close(pfd[1]) != 0) e(0);
|
|
|
|
/*
|
|
* Allow the child to enter the blocking recv(2), and check the pipe
|
|
* to see if it is really blocked.
|
|
*/
|
|
if (usleep(USLEEP_TIME) != 0) e(0);
|
|
|
|
if (read(pfd[0], buf, 1) != -1) e(0);
|
|
if (errno != EAGAIN) e(0);
|
|
|
|
if (edata > 0) {
|
|
if (send(fd[1], &data[idata], edata, 0) != edata) e(0);
|
|
|
|
/*
|
|
* The threshold for the receive is now met if both the minimum
|
|
* is met and MSG_WAITALL was not set (or overridden by
|
|
* MSG_PEEK) or the entire request has been satisfied.
|
|
*/
|
|
if (idata + edata >= min &&
|
|
((flags & (MSG_PEEK | MSG_WAITALL)) != MSG_WAITALL ||
|
|
idata + edata >= len)) {
|
|
if ((fl = fcntl(pfd[0], F_GETFL, 0)) == -1) e(0);
|
|
if (fcntl(pfd[0], F_SETFL, fl & ~O_NONBLOCK) != 0)
|
|
e(0);
|
|
|
|
if (read(pfd[0], &res, sizeof(res)) != sizeof(res))
|
|
e(0);
|
|
if (read(pfd[0], &err, sizeof(err)) != sizeof(err))
|
|
e(0);
|
|
|
|
if (res != MIN(idata + edata, len)) e(0);
|
|
|
|
/* Bail out. */
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Sleep and test once more. */
|
|
if (usleep(USLEEP_TIME) != 0) e(0);
|
|
|
|
if (read(pfd[0], buf, 1) != -1) e(0);
|
|
if (errno != EAGAIN) e(0);
|
|
}
|
|
|
|
if (act < 12) {
|
|
/*
|
|
* Now test various ways to terminate the receive call.
|
|
*/
|
|
switch (tstate) {
|
|
case 0: if (shutdown(fd[0], SHUT_RD) != 0) e(0); break;
|
|
case 1: if (shutdown(fd[1], SHUT_WR) != 0) e(0); break;
|
|
case 2: if (close(fd[1]) != 0) e(0); fd[1] = -1; break;
|
|
case 3: fd[1] = break_recv(fd[1], data, strlen(data)); break;
|
|
}
|
|
} else
|
|
if (kill(pid, SIGUSR1) != 0) e(0);
|
|
|
|
if ((fl = fcntl(pfd[0], F_GETFL, 0)) == -1) e(0);
|
|
if (fcntl(pfd[0], F_SETFL, fl & ~O_NONBLOCK) != 0) e(0);
|
|
|
|
if (read(pfd[0], &res, sizeof(res)) != sizeof(res)) e(0);
|
|
if (read(pfd[0], &err, sizeof(err)) != sizeof(err)) e(0);
|
|
|
|
if (act < 12) {
|
|
/*
|
|
* If there were any data we should have received them now;
|
|
* after all the receive minimum stops being relevant when
|
|
* another condition has been raised. There is one exception:
|
|
* if the receive threshold was never met and we now shut down
|
|
* the socket for reading, EOF is acceptable as return value.
|
|
*/
|
|
if (tstate == 0 && idata + edata < min) {
|
|
if (res != 0) e(0);
|
|
} else if (idata + edata > 0) {
|
|
if (res != MIN(idata + edata, len)) e(0);
|
|
} else if (tstate == 3) {
|
|
if (fd[1] == -1) {
|
|
if (res != -1) e(0);
|
|
if (err != ECONNRESET) e(0);
|
|
} else
|
|
if (res != len) e(0);
|
|
} else
|
|
if (res != 0) e(0);
|
|
} else {
|
|
/*
|
|
* If the receive met the threshold before being interrupted,
|
|
* we should have received at least something. Otherwise, the
|
|
* receive was never admitted and should just return EINTR.
|
|
*/
|
|
if (idata >= min) {
|
|
if (res != MIN(idata, len)) e(0);
|
|
} else {
|
|
if (res != -1) e(0);
|
|
if (err != EINTR) e(0);
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
if (close(pfd[0]) != 0) e(0);
|
|
|
|
if (wait(&status) != pid) e(0);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) e(0);
|
|
|
|
if (fd[1] != -1 && close(fd[1]) != 0) e(0);
|
|
if (close(fd[0]) != 0) e(0);
|
|
}
|
|
|
|
/*
|
|
* Test for receiving on stream sockets. In particular, test SO_RCVLOWAT,
|
|
* MSG_PEEK, MSG_DONTWAIT, and MSG_WAITALL.
|
|
*/
|
|
void
|
|
socklib_stream_recv(int (* socket_pair)(int, int, int, int *), int domain,
|
|
int type, int (* break_recv)(int, const char *, size_t))
|
|
{
|
|
int idata, istate, rlowat, len, bits, act;
|
|
|
|
/* Insanity. */
|
|
for (idata = 0; idata <= MAX_BYTES; idata++)
|
|
for (istate = 0; istate <= 3; istate++)
|
|
for (rlowat = 1; rlowat <= MAX_BYTES; rlowat++)
|
|
for (len = 1; len <= MAX_BYTES; len++)
|
|
for (bits = 0; bits < 8; bits++)
|
|
for (act = 0; act <= 12; act++)
|
|
socklib_stream_recv_sub
|
|
(socket_pair,
|
|
domain, type,
|
|
idata, istate,
|
|
rlowat, len, bits,
|
|
act, break_recv);
|
|
}
|