summaryrefslogtreecommitdiff
path: root/minix/tests/t40e.c
blob: 52a3483c00bf7f90e792bfe2791e1983292dae95 (plain)
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
/* 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.*/
}