#include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "common-socket.h" #define ISO8601_FORMAT "%Y-%m-%dT%H:%M:%S" /* timestamps for debug and error logs */ static char *get_timestamp(void) { struct tm *tm; time_t t; size_t len; char *s; len = sizeof(char) * 32; t = time(NULL); if (t == -1) { return NULL; } tm = gmtime(&t); if (tm == NULL) { return NULL; } s = (char *) malloc(len); if (!s) { perror("malloc"); return NULL; } memset(s, '\0', len); strftime(s, len - 1, ISO8601_FORMAT, tm); return s; } void test_fail_fl(char *msg, char *file, int line) { char *timestamp; timestamp = get_timestamp(); if (errct == 0) fprintf(stderr, "\n"); fprintf(stderr, "[ERROR][%s] (%s Line %d) %s [pid=%d:errno=%d:%s]\n", timestamp, file, line, msg, getpid(), errno, strerror(errno)); fflush(stderr); if (timestamp != NULL) { free(timestamp); timestamp = NULL; } e(7); } #if DEBUG == 1 void debug_fl(char *msg, char *file, int line) { char *timestamp; timestamp = get_timestamp(); fprintf(stdout,"[DEBUG][%s] (%s:%d) %s [pid=%d]\n", timestamp, __FILE__, __LINE__, msg, getpid()); fflush(stdout); if (timestamp != NULL) { free(timestamp); timestamp = NULL; } } #endif void test_socket(const struct socket_test_info *info) { struct stat statbuf, statbuf2; int sd, sd2; int rc; int i; debug("entering test_socket()"); debug("Test socket() with an unsupported address family"); errno = 0; sd = socket(-1, info->type, 0); if (!(sd == -1 && errno == EAFNOSUPPORT)) { test_fail("socket"); if (sd != -1) { CLOSE(sd); } } debug("Test socket() with all available FDs open by this process"); for (i = 3; i < getdtablesize(); i++) { rc = open("/dev/null", O_RDONLY); if (rc == -1) { test_fail("we couldn't open /dev/null for read"); } } errno = 0; sd = socket(info->domain, info->type, 0); if (!(sd == -1 && errno == EMFILE)) { test_fail("socket() call with all fds open should fail"); if (sd != -1) { CLOSE(sd); } } for (i = 3; i < getdtablesize(); i++) { CLOSE(i); } debug("Test socket() with an mismatched protocol"); errno = 0; sd = socket(info->domain, info->type, 4); if (!(sd == -1 && errno == EPROTONOSUPPORT)) { test_fail("socket() should fail with errno = EPROTONOSUPPORT"); if (sd != -1) { CLOSE(sd); } } debug("Test socket() success"); /* * open 2 sockets at once and *then* close them. * This will test that /dev/uds is cloning properly. */ SOCKET(sd, info->domain, info->type, 0); SOCKET(sd2, info->domain, info->type, 0); rc = fstat(sd, &statbuf); if (rc == -1) { test_fail("fstat failed on sd"); } rc = fstat(sd2, &statbuf2); if (rc == -1) { test_fail("fstat failed on sd2"); } if (statbuf.st_dev == statbuf2.st_dev) { test_fail("/dev/uds isn't being cloned"); } CLOSE(sd2); CLOSE(sd); debug("leaving test_socket()"); } void test_getsockname(const struct socket_test_info *info) { int sd; int rc; struct sockaddr_storage sock_addr; socklen_t sock_addr_len; SOCKET(sd, info->domain, info->type, 0); rc = bind(sd, info->serveraddr, info->serveraddrlen); if (rc == -1) { test_fail("bind() should have worked"); } debug("Test getsockname() success"); memset(&sock_addr, '\0', sizeof(sock_addr)); sock_addr_len = sizeof(sock_addr); rc = getsockname(sd, (struct sockaddr *) &sock_addr, &sock_addr_len); if (rc == -1) { test_fail("getsockname() should have worked"); } info->callback_check_sockaddr((struct sockaddr *) &sock_addr, sock_addr_len, "getsockname", 1); CLOSE(sd); } void test_bind(const struct socket_test_info *info) { struct sockaddr_storage sock_addr; socklen_t sock_addr_len; int sd; int sd2; int rc; debug("entering test_bind()"); info->callback_cleanup(); debug("Test bind() success"); SOCKET(sd, info->domain, info->type, 0); rc = bind(sd, info->serveraddr, info->serveraddrlen); if (rc == -1) { test_fail("bind() should have worked"); } debug("Test getsockname() success"); memset(&sock_addr, '\0', sizeof(sock_addr)); sock_addr_len = sizeof(sock_addr); rc = getsockname(sd, (struct sockaddr *) &sock_addr, &sock_addr_len); if (rc == -1) { test_fail("getsockname() should have worked"); } info->callback_check_sockaddr((struct sockaddr *) &sock_addr, sock_addr_len, "getsockname", 1); debug("Test bind() with a address that has already been bind()'d"); SOCKET(sd2, info->domain, info->type, 0); errno = 0; rc = bind(sd2, info->serveraddr, info->serveraddrlen); if (!((rc == -1) && (errno == EADDRINUSE)) && !info->bug_bind_in_use) { test_fail("bind() should have failed with EADDRINUSE"); } CLOSE(sd2); CLOSE(sd); info->callback_cleanup(); if (!info->bug_bind_null) { debug("Test bind() with a NULL address"); SOCKET(sd, info->domain, info->type, 0); errno = 0; rc = bind(sd, (struct sockaddr *) NULL, sizeof(struct sockaddr_storage)); if (!((rc == -1) && (errno == EFAULT))) { test_fail("bind() should have failed with EFAULT"); } CLOSE(sd); } debug("leaving test_bind()"); } void test_listen(const struct socket_test_info *info) { int rc; debug("entering test_listen()"); debug("Test listen() with a bad file descriptor"); errno = 0; rc = listen(-1, 0); if (!(rc == -1 && errno == EBADF)) { test_fail("listen(-1, 0) should have failed"); } debug("Test listen() with a non-socket file descriptor"); errno = 0; rc = listen(0, 0); /* Test on errno disabled here: there's currently no telling what this * will return. POSIX says it should be ENOTSOCK, MINIX3 libc returns * ENOSYS, and we used to test for ENOTTY here.. */ if (!(rc == -1)) { test_fail("listen(0, 0) should have failed"); } debug("leaving test_listen()"); } void test_shutdown(const struct socket_test_info *info) { int how[3] = { SHUT_RD, SHUT_WR, SHUT_RDWR }; int sd; int rc; int i; debug("entering test_shutdown()"); /* test for each direction (read, write, read-write) */ for (i = 0; i < 3; i++) { if (info->bug_shutdown_read && how[i] == SHUT_RD) continue; debug("test shutdown() with an invalid descriptor"); errno = 0; rc = shutdown(-1, how[i]); if (!(rc == -1 && errno == EBADF) && !info->bug_shutdown) { test_fail("shutdown(-1, how[i]) should have failed"); } debug("test shutdown() with a non-socket descriptor"); errno = 0; rc = shutdown(0, how[i]); if (!(rc == -1 && errno == ENOSYS) && !info->bug_shutdown) { test_fail("shutdown() should have failed with ENOSYS"); } debug("test shutdown() with a socket that is not connected"); SOCKET(sd, info->domain, info->type, 0); errno = 0; rc = shutdown(sd, how[i]); if (!(rc == -1 && errno == ENOTCONN) && !info->bug_shutdown_not_conn && !info->bug_shutdown) { test_fail("shutdown() should have failed"); } CLOSE(sd); } SOCKET(sd, info->domain, info->type, 0); errno = 0; rc = shutdown(sd, -1); if (!(rc == -1 && errno == ENOTCONN) && !info->bug_shutdown_not_conn && !info->bug_shutdown) { test_fail("shutdown(sd, -1) should have failed with ENOTCONN"); } CLOSE(sd); debug("leaving test_shutdown()"); } void test_close(const struct socket_test_info *info) { int sd, sd2; int rc, i; debug("entering test_close()"); info->callback_cleanup(); debug("Test close() success"); SOCKET(sd, info->domain, info->type, 0); rc = bind(sd, info->serveraddr, info->serveraddrlen); if (rc != 0) { test_fail("bind() should have worked"); } CLOSE(sd); debug("Close an already closed file descriptor"); errno = 0; rc = close(sd); if (!(rc == -1 && errno == EBADF)) { test_fail("close(sd) should have failed with EBADF"); } info->callback_cleanup(); debug("dup()'ing a file descriptor and closing both should work"); SOCKET(sd, info->domain, info->type, 0); rc = bind(sd, info->serveraddr, info->serveraddrlen); if (rc != 0) { test_fail("bind() should have worked"); } errno = 0; sd2 = dup(sd); if (sd2 == -1) { test_fail("dup(sd) should have worked"); } else { CLOSE(sd2); CLOSE(sd); } info->callback_cleanup(); /* Create and close a socket a bunch of times. * If the implementation doesn't properly free the * socket during close(), eventually socket() will * fail when the internal descriptor table is full. */ for (i = 0; i < 1024; i++) { SOCKET(sd, info->domain, info->type, 0); CLOSE(sd); } debug("leaving test_close()"); } void test_sockopts(const struct socket_test_info *info) { int i; int rc; int sd; int option_value; socklen_t option_len; debug("entering test_sockopts()"); for (i = 0; i < info->typecount; i++) { SOCKET(sd, info->domain, info->types[i], 0); debug("Test setsockopt() works"); option_value = 0; option_len = sizeof(option_value); errno = 0; rc = getsockopt(sd, SOL_SOCKET, SO_TYPE, &option_value, &option_len); if (rc != 0) { test_fail("setsockopt() should have worked"); } if (option_value != info->types[i]) { test_fail("SO_TYPE didn't seem to work."); } CLOSE(sd); } SOCKET(sd, info->domain, info->type, 0); debug("Test setsockopt() works"); option_value = 0; option_len = sizeof(option_value); errno = 0; rc = getsockopt(sd, SOL_SOCKET, SO_SNDBUF, &option_value, &option_len); if (rc != 0 && !info->bug_sockopt_sndbuf) { test_fail("getsockopt() should have worked"); } if (info->expected_sndbuf >= 0 && option_value != info->expected_sndbuf && !info->bug_sockopt_sndbuf) { test_fail("SO_SNDBUF didn't seem to work."); } CLOSE(sd); SOCKET(sd, info->domain, info->type, 0); debug("Test setsockopt() works"); option_value = 0; option_len = sizeof(option_value); errno = 0; rc = getsockopt(sd, SOL_SOCKET, SO_RCVBUF, &option_value, &option_len); if (rc != 0 && !info->bug_sockopt_rcvbuf) { test_fail("getsockopt() should have worked"); } if (info->expected_rcvbuf >= 0 && option_value != info->expected_rcvbuf && !info->bug_sockopt_rcvbuf) { test_fail("SO_RCVBUF didn't seem to work."); } CLOSE(sd); debug("leaving test_sockopts()"); } void test_read(const struct socket_test_info *info) { int rc; int fd; char buf[BUFSIZE]; debug("entering test_read()"); errno = 0; rc = read(-1, buf, sizeof(buf)); if (!(rc == -1 && errno == EBADF)) { test_fail("read() should have failed with EBADF"); } fd = open("/tmp", O_RDONLY); if (fd == -1) { test_fail("open(\"/tmp\", O_RDONLY) should have worked"); } CLOSE(fd); debug("leaving test_read()"); } void test_write(const struct socket_test_info *info) { int rc; char buf[BUFSIZE]; debug("entering test_write()"); errno = 0; rc = write(-1, buf, sizeof(buf)); if (!(rc == -1 && errno == EBADF)) { test_fail("write() should have failed with EBADF"); } debug("leaving test_write()"); } void test_dup(const struct socket_test_info *info) { struct stat info1; struct stat info2; int sd, sd2; int rc; int i; debug("entering test_dup()"); info->callback_cleanup(); debug("Test dup()"); SOCKET(sd, info->domain, info->type, 0); rc = bind(sd, info->serveraddr, info->serveraddrlen); if (rc != 0) { test_fail("bind() should have worked"); } errno = 0; sd2 = dup(sd); if (sd2 == -1) { test_fail("dup(sd) should have worked"); } rc = fstat(sd, &info1); if (rc == -1) { test_fail("fstat(fd, &info1) failed"); } rc = fstat(sd2, &info2); if (rc == -1) { test_fail("fstat(sd, &info2) failed"); } if (info1.st_ino != info2.st_ino) { test_fail("dup() failed info1.st_ino != info2.st_ino"); } CLOSE(sd); CLOSE(sd2); debug("Test dup() with a closed socket"); errno = 0; rc = dup(sd); if (!(rc == -1 && errno == EBADF)) { test_fail("dup(sd) on a closed socket shouldn't have worked"); } debug("Test dup() with socket descriptor of -1"); errno = 0; rc = dup(-1); if (!(rc == -1 && errno == EBADF)) { test_fail("dup(-1) shouldn't have worked"); } debug("Test dup() when all of the file descriptors are taken"); SOCKET(sd, info->domain, info->type, 0); for (i = 4; i < getdtablesize(); i++) { rc = open("/dev/null", O_RDONLY); if (rc == -1) { test_fail("we couldn't open /dev/null for read"); } } errno = 0; sd2 = dup(sd); if (!(sd2 == -1 && errno == EMFILE)) { test_fail("dup(sd) should have failed with errno = EMFILE"); } for (i = 3; i < getdtablesize(); i++) { CLOSE(i); } info->callback_cleanup(); debug("leaving test_dup()"); } void test_dup2(const struct socket_test_info *info) { struct stat info1; struct stat info2; int sd; int fd; int rc; debug("entering test_dup2()"); info->callback_cleanup(); SOCKET(sd, info->domain, info->type, 0); rc = bind(sd, info->serveraddr, info->serveraddrlen); if (rc != 0) { test_fail("bind() should have worked"); } fd = open("/dev/null", O_RDONLY); if (fd == -1) { test_fail("open(\"/dev/null\", O_RDONLY) failed"); } fd = dup2(sd, fd); if (fd == -1) { test_fail("dup2(sd, fd) failed."); } memset(&info1, '\0', sizeof(struct stat)); memset(&info2, '\0', sizeof(struct stat)); rc = fstat(fd, &info1); if (rc == -1) { test_fail("fstat(fd, &info1) failed"); } rc = fstat(sd, &info2); if (rc == -1) { test_fail("fstat(sd, &info2) failed"); } if (!(info1.st_ino == info2.st_ino && major(info1.st_dev) == major(info2.st_dev) && minor(info1.st_dev) == minor(info2.st_dev))) { test_fail("dup2() failed"); } CLOSE(fd); CLOSE(sd); info->callback_cleanup(); debug("leaving test_dup2()"); } /* * A toupper() server. This toy server converts a string to upper case. */ static void test_xfer_server(const struct socket_test_info *info, pid_t pid) { int i; struct timeval tv; fd_set readfds; int status; int rc; int sd; unsigned char buf[BUFSIZE]; socklen_t client_addr_size; int client_sd; struct sockaddr_storage client_addr; status = 0; rc = 0; sd = 0; client_sd = 0; client_addr_size = sizeof(struct sockaddr_storage); memset(&buf, '\0', sizeof(buf)); memset(&client_addr, '\0', sizeof(client_addr)); SOCKET(sd, info->domain, info->type, 0); rc = bind(sd, info->serveraddr, info->serveraddrlen); if (rc == -1) { test_fail("bind() should have worked"); } rc = listen(sd, 8); if (rc == -1) { test_fail("listen(sd, 8) should have worked"); } /* we're ready for connections, time to tell the client to start * the test */ kill(pid, SIGUSR1); tv.tv_sec = 10; tv.tv_usec = 0; FD_ZERO(&readfds); FD_SET(sd, &readfds); /* use select() in case the client is really broken and never * attempts to connect (we don't want to block on accept() * forever). */ rc = select(sd + 1, &readfds, NULL, NULL, &tv); if (rc == -1) { test_fail("[server] select() should not have failed"); } if (rc != 1) { test_fail("[server] select() should have returned 1"); printf("[server] select returned %d\n", rc); } if (!(FD_ISSET(sd, &readfds))) { test_fail("[server] client didn't connect within 10 seconds"); kill(pid, SIGKILL); return; } client_sd = accept(sd, (struct sockaddr *) &client_addr, &client_addr_size); if (client_sd == -1) { test_fail("accept() should have worked"); kill(pid, SIGKILL); return; } else { debug("[server] client accept()'d"); } debug("[server] Reading message"); rc = read(client_sd, buf, sizeof(buf)); if (rc == -1) { test_fail("read() failed unexpectedly"); kill(pid, SIGKILL); return; } debug("[server] we got the following message:"); debug(buf); for (i = 0; i < rc && i < 127; i++) { buf[i] = toupper(buf[i]); } debug("[server] Writing message..."); rc = write(client_sd, buf, sizeof(buf)); if (rc == -1) { test_fail("write(client_sd, buf, sizeof(buf)) failed"); kill(pid, SIGKILL); return; } if (rc < strlen(buf)) { test_fail("[server] write didn't write all the bytes"); } memset(&buf, '\0', sizeof(buf)); debug("[server] Recv message"); rc = recv(client_sd, buf, sizeof(buf), 0); if (rc == -1) { test_fail("recv() failed unexpectedly"); kill(pid, SIGKILL); return; } debug("[server] we got the following message:"); debug(buf); for (i = 0; i < rc && i < 127; i++) { buf[i] = toupper(buf[i]); } debug("[server] Sending message..."); rc = send(client_sd, buf, sizeof(buf), 0); if (rc == -1) { test_fail("send(client_sd, buf, sizeof(buf), 0) failed"); kill(pid, SIGKILL); return; } if (rc < strlen(buf)) { test_fail("[server] write didn't write all the bytes"); } memset(&buf, '\0', sizeof(buf)); debug("[server] Recvfrom message"); rc = recvfrom(client_sd, buf, sizeof(buf), 0, NULL, 0); if (rc == -1) { test_fail("recvfrom() failed unexpectedly"); kill(pid, SIGKILL); return; } debug("[server] we got the following message:"); debug(buf); for (i = 0; i < rc && i < 127; i++) { buf[i] = toupper(buf[i]); } debug("[server] Sendto message..."); rc = sendto(client_sd, buf, sizeof(buf), 0, NULL, 0); if (rc == -1) { test_fail("sendto() failed"); kill(pid, SIGKILL); return; } if (rc < strlen(buf)) { test_fail("[server] write didn't write all the bytes"); } shutdown(client_sd, SHUT_RDWR); CLOSE(client_sd); shutdown(sd, SHUT_RDWR); CLOSE(sd); /* wait for client to exit */ do { errno = 0; rc = waitpid(pid, &status, 0); } while (rc == -1 && errno == EINTR); /* we use the exit status to get its error count */ errct += WEXITSTATUS(status); } int server_ready = 0; /* signal handler for the client */ void test_xfer_sighdlr(int sig) { debug("entering signal handler"); switch (sig) { /* the server will send SIGUSR1 when it is time for us * to start the tests */ case SIGUSR1: server_ready = 1; debug("got SIGUSR1, the server is ready for the client"); break; default: debug("didn't get SIGUSR1"); } debug("leaving signal handler"); } /* * A toupper() client. */ static void test_xfer_client(const struct socket_test_info *info) { struct timeval tv; fd_set readfds; struct sockaddr_storage peer_addr; socklen_t peer_addr_len; int sd; int rc; char buf[BUFSIZE]; debug("[client] entering test_xfer_client()"); errct = 0; /* reset error count */ memset(&buf, '\0', sizeof(buf)); while (server_ready == 0) { debug("[client] waiting for the server to signal"); sleep(1); } peer_addr_len = sizeof(peer_addr); if (info->callback_xfer_prepclient) info->callback_xfer_prepclient(); debug("[client] creating client socket"); SOCKET(sd, info->domain, info->type, 0); debug("[client] connecting to server through the symlink"); rc = connect(sd, info->clientaddrsym, info->clientaddrsymlen); if (rc == -1) { test_fail("[client] connect() should have worked"); } else { debug("[client] connected"); } debug("[client] testing getpeername()"); memset(&peer_addr, '\0', sizeof(peer_addr)); rc = getpeername(sd, (struct sockaddr *) &peer_addr, &peer_addr_len); if (rc == -1) { test_fail("[client] getpeername() should have worked"); } /* we need to use the full path "/usr/src/test/DIR_56/test.sock" * because that is what is returned by getpeername(). */ info->callback_check_sockaddr((struct sockaddr *) &peer_addr, peer_addr_len, "getpeername", 1); strncpy(buf, "Hello, World!", sizeof(buf) - 1); debug("[client] send to server"); rc = write(sd, buf, sizeof(buf)); if (rc == -1) { test_fail("[client] write() failed unexpectedly"); } memset(buf, '\0', sizeof(buf)); debug("[client] read from server"); rc = read(sd, buf, sizeof(buf)); if (rc == -1) { test_fail("[client] read() failed unexpectedly"); } else { debug("[client] we got the following message:"); debug(buf); } if (strncmp(buf, "HELLO, WORLD!", sizeof(buf)) != 0) { test_fail("[client] We didn't get the correct response"); } memset(&buf, '\0', sizeof(buf)); strncpy(buf, "Bonjour!", sizeof(buf) - 1); debug("[client] send to server"); rc = send(sd, buf, sizeof(buf), 0); if (rc == -1) { test_fail("[client] send() failed unexpectedly"); } if (info->callback_xfer_peercred) info->callback_xfer_peercred(sd); debug("Testing select()"); tv.tv_sec = 2; tv.tv_usec = 500000; FD_ZERO(&readfds); FD_SET(sd, &readfds); rc = select(sd + 1, &readfds, NULL, NULL, &tv); if (rc == -1) { test_fail("[client] select() should not have failed"); } if (rc != 1) { test_fail("[client] select() should have returned 1"); } if (!(FD_ISSET(sd, &readfds))) { test_fail("The server didn't respond within 2.5 seconds"); } memset(buf, '\0', sizeof(buf)); debug("[client] recv from server"); rc = recv(sd, buf, sizeof(buf), 0); if (rc == -1) { test_fail("[client] recv() failed unexpectedly"); } else { debug("[client] we got the following message:"); debug(buf); } if (strncmp(buf, "BONJOUR!", sizeof(buf)) != 0) { test_fail("[client] We didn't get the right response."); } memset(&buf, '\0', sizeof(buf)); strncpy(buf, "Hola!", sizeof(buf) - 1); debug("[client] sendto to server"); rc = sendto(sd, buf, sizeof(buf), 0, NULL, 0); if (rc == -1) { test_fail("[client] sendto() failed"); } debug("Testing select()"); tv.tv_sec = 2; tv.tv_usec = 500000; FD_ZERO(&readfds); FD_SET(sd, &readfds); rc = select(sd + 1, &readfds, NULL, NULL, &tv); if (rc == -1) { test_fail("[client] select() should not have failed"); } if (rc != 1) { test_fail("[client] select() should have returned 1"); } if (!(FD_ISSET(sd, &readfds))) { test_fail("[client] The server didn't respond in 2.5 seconds"); } memset(buf, '\0', sizeof(buf)); debug("[client] recvfrom from server"); rc = recvfrom(sd, buf, sizeof(buf), 0, NULL, 0); if (rc == -1) { test_fail("[cleint] recvfrom() failed unexpectedly"); } else { debug("[client] we got the following message:"); debug(buf); } if (strncmp(buf, "HOLA!", sizeof(buf)) != 0) { test_fail("[client] We didn't get the right response."); } debug("[client] closing socket"); CLOSE(sd); debug("[client] leaving test_xfer_client()"); exit(errct); } void test_xfer(const struct socket_test_info *info) { pid_t pid; debug("entering test_xfer()"); info->callback_cleanup(); /* the signal handler is only used by the client, but we have to * install it now. if we don't the server may signal the client * before the handler is installed. */ debug("installing signal handler"); if (signal(SIGUSR1, test_xfer_sighdlr) == SIG_ERR) { test_fail("signal(SIGUSR1, test_xfer_sighdlr) failed"); } debug("signal handler installed"); server_ready = 0; pid = fork(); if (pid == -1) { test_fail("fork() failed"); return; } else if (pid == 0) { debug("child"); errct = 0; test_xfer_client(info); test_fail("we should never get here"); exit(1); } else { debug("parent"); test_xfer_server(info, pid); debug("parent done"); } info->callback_cleanup(); debug("leaving test_xfer()"); } static void test_simple_client(const struct socket_test_info *info, int type) { char buf[BUFSIZE]; int sd, rc; sd = socket(info->domain, type, 0); if (sd == -1) { test_fail("socket"); exit(errct); } while (server_ready == 0) { debug("[client] waiting for the server"); sleep(1); } bzero(buf, BUFSIZE); snprintf(buf, BUFSIZE-1, "Hello, My Name is Client."); if (type == SOCK_DGRAM) { rc = sendto(sd, buf, strlen(buf) + 1, 0, info->clientaddr, info->clientaddrlen); if (rc == -1) { test_fail("sendto"); exit(errct); } } else { rc = connect(sd, info->clientaddr, info->clientaddrlen); if (rc == -1) { test_fail("connect"); exit(errct); } rc = write(sd, buf, strlen(buf) + 1); if (rc == -1) { test_fail("write"); } memset(buf, '\0', BUFSIZE); rc = read(sd, buf, BUFSIZE); if (rc == -1) { test_fail("read"); } if (strcmp("Hello, My Name is Server.", buf) != 0) { test_fail("didn't read the correct string"); } } rc = close(sd); if (rc == -1) { test_fail("close"); } exit(errct); } static void test_simple_server(const struct socket_test_info *info, int type, pid_t pid) { char buf[BUFSIZE]; int sd, rc, client_sd, status; struct sockaddr_storage addr; socklen_t addr_len; addr_len = info->clientaddrlen; sd = socket(info->domain, type, 0); if (sd == -1) { test_fail("socket"); } assert(info->clientaddrlen <= sizeof(addr)); memcpy(&addr, info->clientaddr, info->clientaddrlen); rc = bind(sd, info->serveraddr, info->serveraddrlen); if (rc == -1) { test_fail("bind"); } if (type == SOCK_DGRAM) { /* ready for client */ kill(pid, SIGUSR1); rc = recvfrom(sd, buf, BUFSIZE, 0, (struct sockaddr *) &addr, &addr_len); if (rc == -1) { test_fail("recvfrom"); } } else { rc = listen(sd, 5); if (rc == -1) { test_fail("listen"); } /* we're ready for connections, time to tell the client * to start the test */ kill(pid, SIGUSR1); client_sd = accept(sd, (struct sockaddr *) &addr, &addr_len); if (client_sd == -1) { test_fail("accept"); } memset(buf, '\0', BUFSIZE); rc = read(client_sd, buf, BUFSIZE); if (rc == -1) { test_fail("read"); } if (strcmp("Hello, My Name is Client.", buf) != 0) { test_fail("didn't read the correct string"); } /* added for extra fun to make the client block on read() */ sleep(1); bzero(buf, BUFSIZE); snprintf(buf, BUFSIZE-1, "Hello, My Name is Server."); rc = write(client_sd, buf, strlen(buf) + 1); if (rc == -1) { test_fail("write"); } rc = close(client_sd); if (rc == -1) { test_fail("close"); } } rc = close(sd); if (rc == -1) { test_fail("close"); } /* wait for client to exit */ do { errno = 0; rc = waitpid(pid, &status, 0); } while (rc == -1 && errno == EINTR); /* we use the exit status to get its error count */ errct += WEXITSTATUS(status); } static void test_abort_client(const struct socket_test_info *info, int abort_type); static void test_abort_server(const struct socket_test_info *info, pid_t pid, int abort_type); void test_abort_client_server(const struct socket_test_info *info, int abort_type) { pid_t pid; debug("test_simple_client_server()"); info->callback_cleanup(); /* the signal handler is only used by the client, but we have to * install it now. if we don't the server may signal the client * before the handler is installed. */ debug("installing signal handler"); if (signal(SIGUSR1, test_xfer_sighdlr) == SIG_ERR) { test_fail("signal(SIGUSR1, test_xfer_sighdlr) failed"); } debug("signal handler installed"); server_ready = 0; pid = fork(); if (pid == -1) { test_fail("fork() failed"); return; } else if (pid == 0) { debug("child"); errct = 0; test_abort_client(info, abort_type); test_fail("we should never get here"); exit(1); } else { debug("parent"); test_abort_server(info, pid, abort_type); debug("parent done"); } info->callback_cleanup(); } static void test_abort_client(const struct socket_test_info *info, int abort_type) { char buf[BUFSIZE]; int sd, rc; sd = socket(info->domain, info->type, 0); if (sd == -1) { test_fail("socket"); exit(errct); } while (server_ready == 0) { debug("[client] waiting for the server"); sleep(1); } bzero(buf, BUFSIZE); snprintf(buf, BUFSIZE-1, "Hello, My Name is Client."); rc = connect(sd, info->clientaddr, info->clientaddrlen); if (rc == -1) { test_fail("connect"); exit(errct); } if (abort_type == 2) { /* Give server a chance to close connection */ sleep(2); rc = write(sd, buf, strlen(buf) + 1); if (rc != -1) { if (!info->ignore_write_conn_reset) { test_fail("write should have failed\n"); } } else if (errno != ECONNRESET) { test_fail("errno should've been ECONNRESET\n"); } } rc = close(sd); if (rc == -1) { test_fail("close"); } exit(errct); } static void test_abort_server(const struct socket_test_info *info, pid_t pid, int abort_type) { char buf[BUFSIZE]; int sd, rc, client_sd, status; struct sockaddr_storage addr; socklen_t addr_len; addr_len = info->clientaddrlen; sd = socket(info->domain, info->type, 0); if (sd == -1) { test_fail("socket"); } assert(sizeof(addr) >= info->clientaddrlen); memcpy(&addr, info->clientaddr, info->clientaddrlen); rc = bind(sd, info->serveraddr, info->serveraddrlen); if (rc == -1) { test_fail("bind"); } rc = listen(sd, 5); if (rc == -1) { test_fail("listen"); } /* we're ready for connections, time to tell the client * to start the test */ kill(pid, SIGUSR1); client_sd = accept(sd, (struct sockaddr *) &addr, &addr_len); if (client_sd == -1) { test_fail("accept"); } if (abort_type == 1) { memset(buf, '\0', BUFSIZE); rc = read(client_sd, buf, BUFSIZE); if (rc != -1 && (rc != 0 || !info->ignore_read_conn_reset)) { test_fail("read should've failed or returned zero\n"); } if (rc != 0 && errno != ECONNRESET) { test_fail("errno should've been ECONNRESET\n"); } } /* else if (abort_type == 2) { */ rc = close(client_sd); if (rc == -1) { test_fail("close"); } /* } */ rc = close(sd); if (rc == -1) { test_fail("close"); } /* wait for client to exit */ do { errno = 0; rc = waitpid(pid, &status, 0); } while (rc == -1 && errno == EINTR); /* we use the exit status to get its error count */ errct += WEXITSTATUS(status); } void test_simple_client_server(const struct socket_test_info *info, int type) { pid_t pid; debug("entering test_simple_client_server()"); info->callback_cleanup(); /* the signal handler is only used by the client, but we have to * install it now. if we don't the server may signal the client * before the handler is installed. */ debug("installing signal handler"); if (signal(SIGUSR1, test_xfer_sighdlr) == SIG_ERR) { test_fail("signal(SIGUSR1, test_xfer_sighdlr) failed"); } debug("signal handler installed"); server_ready = 0; pid = fork(); if (pid == -1) { test_fail("fork() failed"); return; } else if (pid == 0) { debug("child"); errct = 0; test_simple_client(info, type); test_fail("we should never get here"); exit(1); } else { debug("parent"); test_simple_server(info, type, pid); debug("parent done"); } info->callback_cleanup(); debug("leaving test_simple_client_server()"); } void test_msg_dgram(const struct socket_test_info *info) { int rc; int src; int dst; struct sockaddr_storage addr; struct iovec iov[3]; struct msghdr msg1; struct msghdr msg2; char buf1[BUFSIZE]; char buf2[BUFSIZE]; char buf3[BUFSIZE]; debug("entering test_msg_dgram"); info->callback_cleanup(); src = socket(info->domain, SOCK_DGRAM, 0); if (src == -1) { test_fail("socket"); } dst = socket(info->domain, SOCK_DGRAM, 0); if (dst == -1) { test_fail("socket"); } rc = bind(src, info->serveraddr2, info->serveraddr2len); if (rc == -1) { test_fail("bind"); } assert(info->clientaddrlen <= sizeof(addr)); memcpy(&addr, info->clientaddr, info->clientaddrlen); rc = bind(dst, info->serveraddr, info->serveraddrlen); if (rc == -1) { test_fail("bind"); } memset(&buf1, '\0', BUFSIZE); memset(&buf2, '\0', BUFSIZE); memset(&buf3, '\0', BUFSIZE); strncpy(buf1, "Minix ", BUFSIZE-1); strncpy(buf2, "is ", BUFSIZE-1); strncpy(buf3, "great!", BUFSIZE-1); iov[0].iov_base = buf1; iov[0].iov_len = 6; iov[1].iov_base = buf2; iov[1].iov_len = 3; iov[2].iov_base = buf3; iov[2].iov_len = 32; memset(&msg1, '\0', sizeof(struct msghdr)); msg1.msg_name = &addr; msg1.msg_namelen = info->clientaddrlen; msg1.msg_iov = iov; msg1.msg_iovlen = 3; msg1.msg_control = NULL; msg1.msg_controllen = 0; msg1.msg_flags = 0; rc = sendmsg(src, &msg1, 0); if (rc == -1) { test_fail("sendmsg"); } memset(&buf1, '\0', BUFSIZE); memset(&buf2, '\0', BUFSIZE); iov[0].iov_base = buf1; iov[0].iov_len = 9; iov[1].iov_base = buf2; iov[1].iov_len = 32; memset(&addr, '\0', sizeof(addr)); memset(&msg2, '\0', sizeof(struct msghdr)); msg2.msg_name = &addr; msg2.msg_namelen = sizeof(addr); msg2.msg_iov = iov; msg2.msg_iovlen = 2; msg2.msg_control = NULL; msg2.msg_controllen = 0; msg2.msg_flags = 0; rc = recvmsg(dst, &msg2, 0); if (rc == -1) { test_fail("recvmsg"); } if (strncmp(buf1, "Minix is ", 9) || strncmp(buf2, "great!", 6)) { test_fail("recvmsg"); } /* we need to use the full path "/usr/src/test/DIR_56/testb.sock" * because that is what is returned by recvmsg(). */ info->callback_check_sockaddr((struct sockaddr *) &addr, msg2.msg_namelen, "recvmsg", 2); rc = close(dst); if (rc == -1) { test_fail("close"); } rc = close(src); if (rc == -1) { test_fail("close"); } info->callback_cleanup(); debug("leaving test_msg_dgram"); } #define check_select(sd, rd, wr, block) \ check_select_internal(sd, rd, wr, block, 1, __LINE__) #define check_select_cond(sd, rd, wr, block, allchecks) \ check_select_internal(sd, rd, wr, block, allchecks, __LINE__) static void check_select_internal(int sd, int rd, int wr, int block, int allchecks, int line) { fd_set read_set, write_set; struct timeval tv; FD_ZERO(&read_set); if (rd != -1) FD_SET(sd, &read_set); FD_ZERO(&write_set); if (wr != -1) FD_SET(sd, &write_set); tv.tv_sec = block ? 2 : 0; tv.tv_usec = 0; errno = 0; if (select(sd + 1, &read_set, &write_set, NULL, &tv) < 0) test_fail_fl("select() failed unexpectedly", __FILE__, line); if (rd != -1 && !!FD_ISSET(sd, &read_set) != rd && allchecks) test_fail_fl("select() mismatch on read operation", __FILE__, line); if (wr != -1 && !!FD_ISSET(sd, &write_set) != wr && allchecks) test_fail_fl("select() mismatch on write operation", __FILE__, line); } /* * Verify that: * - a nonblocking connecting socket for which there is no accepter, will * return EINPROGRESS and complete in the background later; * - a nonblocking listening socket will return EAGAIN on accept; * - connecting a connecting socket yields EALREADY; * - connecting a connected socket yields EISCONN; * - selecting for read and write on a connecting socket will only satisfy the * write only once it is connected; * - doing a nonblocking write on a connecting socket yields EAGAIN; * - doing a nonblocking read on a connected socket with no pending data yields * EAGAIN. */ void test_nonblock(const struct socket_test_info *info) { char buf[BUFSIZE]; socklen_t len; int server_sd, client_sd; struct sockaddr_storage addr; int status; debug("entering test_nonblock()"); memset(buf, 0, sizeof(buf)); SOCKET(server_sd, info->domain, info->type, 0); if (bind(server_sd, info->serveraddr, info->serveraddrlen) == -1) test_fail("bind() should have worked"); if (listen(server_sd, 8) == -1) test_fail("listen() should have worked"); fcntl(server_sd, F_SETFL, fcntl(server_sd, F_GETFL) | O_NONBLOCK); check_select(server_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/); len = sizeof(addr); if (accept(server_sd, (struct sockaddr *) &addr, &len) != -1 || errno != EAGAIN) test_fail("accept() should have yielded EAGAIN"); SOCKET(client_sd, info->domain, info->type, 0); fcntl(client_sd, F_SETFL, fcntl(client_sd, F_GETFL) | O_NONBLOCK); if (connect(client_sd, info->clientaddr, info->clientaddrlen) != -1) { test_fail("connect() should have failed"); } else if (errno != EINPROGRESS) { test_fail("connect() should have yielded EINPROGRESS"); } check_select_cond(client_sd, 0 /*read*/, 0 /*write*/, 0 /*block*/, !info->ignore_select_delay); if (connect(client_sd, info->clientaddr, info->clientaddrlen) != -1) { test_fail("connect() should have failed"); } else if (errno != EALREADY && errno != EISCONN) { test_fail("connect() should have yielded EALREADY"); } if (recv(client_sd, buf, sizeof(buf), 0) != -1 || errno != EAGAIN) test_fail("recv() should have yielded EAGAIN"); /* This may be an implementation aspect, or even plain wrong (?). */ if (send(client_sd, buf, sizeof(buf), 0) != -1) { if (!info->ignore_send_waiting) { test_fail("send() should have failed"); } } else if (errno != EAGAIN) { test_fail("send() should have yielded EAGAIN"); } switch (fork()) { case 0: errct = 0; close(client_sd); check_select(server_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/); len = sizeof(addr); client_sd = accept(server_sd, (struct sockaddr *) &addr, &len); if (client_sd == -1) test_fail("accept() should have succeeded"); check_select(server_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/); close(server_sd); /* Let the socket become writable in the parent process. */ sleep(1); if (write(client_sd, buf, 1) != 1) test_fail("write() should have succeeded"); /* Wait for the client side to close. */ check_select_cond(client_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/, !info->ignore_select_delay /*allchecks*/); check_select(client_sd, 1 /*read*/, -1 /*write*/, 1 /*block*/); check_select(client_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/); exit(errct); case -1: test_fail("can't fork"); default: break; } close(server_sd); check_select(client_sd, 0 /*read*/, 1 /*write*/, 1 /*block*/); check_select(client_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/); if (connect(client_sd, info->clientaddr, info->clientaddrlen) != -1 || errno != EISCONN) test_fail("connect() should have yielded EISCONN"); check_select(client_sd, 1 /*read*/, -1 /*write*/, 1 /*block*/); check_select(client_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/); if (read(client_sd, buf, 1) != 1) test_fail("read() should have succeeded"); check_select(client_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/); if (read(client_sd, buf, 1) != -1 || errno != EAGAIN) test_fail("read() should have yielded EAGAIN"); /* Let the child process block on the select waiting for the close. */ sleep(1); close(client_sd); errno = 0; if (wait(&status) <= 0) test_fail("wait() should have succeeded"); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) test_fail("child process failed the test"); info->callback_cleanup(); debug("leaving test_nonblock()"); } /* * Verify that a nonblocking connect for which there is an accepter, succeeds * immediately. A pretty lame test, only here for completeness. */ void test_connect_nb(const struct socket_test_info *info) { socklen_t len; int server_sd, client_sd; struct sockaddr_storage addr; int status; debug("entering test_connect_nb()"); SOCKET(server_sd, info->domain, info->type, 0); if (bind(server_sd, info->serveraddr, info->serveraddrlen) == -1) test_fail("bind() should have worked"); if (listen(server_sd, 8) == -1) test_fail("listen() should have worked"); switch (fork()) { case 0: errct = 0; len = sizeof(addr); if (accept(server_sd, (struct sockaddr *) &addr, &len) == -1) test_fail("accept() should have succeeded"); exit(errct); case -1: test_fail("can't fork"); default: break; } close(server_sd); sleep(1); SOCKET(client_sd, info->domain, info->type, 0); fcntl(client_sd, F_SETFL, fcntl(client_sd, F_GETFL) | O_NONBLOCK); if (connect(client_sd, info->clientaddr, info->clientaddrlen) != 0) { if (!info->ignore_connect_delay) { test_fail("connect() should have succeeded"); } else if (errno != EINPROGRESS) { test_fail("connect() should have succeeded or " "failed with EINPROGRESS"); } } close(client_sd); if (wait(&status) <= 0) test_fail("wait() should have succeeded"); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) test_fail("child process failed the test"); info->callback_cleanup(); debug("leaving test_connect_nb()"); } static void dummy_handler(int sig) { /* Nothing. */ } /* * Verify that: * - interrupting a blocking connect will return EINTR but complete in the * background later; * - doing a blocking write on an asynchronously connecting socket succeeds * once the socket is connected. * - doing a nonblocking write on a connected socket with lots of pending data * yields EAGAIN. */ void test_intr(const struct socket_test_info *info) { struct sigaction act, oact; char buf[BUFSIZE]; int isconn; socklen_t len; int server_sd, client_sd; struct sockaddr_storage addr; int r, status; debug("entering test_intr()"); memset(buf, 0, sizeof(buf)); SOCKET(server_sd, info->domain, info->type, 0); if (bind(server_sd, info->serveraddr, info->serveraddrlen) == -1) test_fail("bind() should have worked"); if (listen(server_sd, 8) == -1) test_fail("listen() should have worked"); SOCKET(client_sd, info->domain, info->type, 0); memset(&act, 0, sizeof(act)); act.sa_handler = dummy_handler; if (sigaction(SIGALRM, &act, &oact) == -1) test_fail("sigaction() should have succeeded"); if (info->domain != PF_INET) alarm(1); isconn = 0; if (connect(client_sd, info->clientaddr, info->clientaddrlen) != -1) { if (!info->ignore_connect_unaccepted) { test_fail("connect() should have failed"); } isconn = 1; } else if (errno != EINTR) { test_fail("connect() should have yielded EINTR"); } alarm(0); check_select(client_sd, 0 /*read*/, isconn /*write*/, 0 /*block*/); switch (fork()) { case 0: errct = 0; close(client_sd); check_select(server_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/); len = sizeof(addr); client_sd = accept(server_sd, (struct sockaddr *) &addr, &len); if (client_sd == -1) test_fail("accept() should have succeeded"); check_select(server_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/); close(server_sd); check_select(client_sd, 1 /*read*/, -1 /*write*/, 1 /*block*/); check_select(client_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/); if (recv(client_sd, buf, sizeof(buf), 0) != sizeof(buf)) test_fail("recv() should have yielded bytes"); /* No partial transfers should be happening. */ check_select(client_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/); sleep(1); fcntl(client_sd, F_SETFL, fcntl(client_sd, F_GETFL) | O_NONBLOCK); /* We can only test nonblocking writes by filling the pipe. */ while ((r = write(client_sd, buf, sizeof(buf))) > 0); if (r != -1) { test_fail("write() should have failed"); } else if (errno != EAGAIN) { test_fail("write() should have yielded EAGAIN"); } check_select(client_sd, 0 /*read*/, 0 /*write*/, 0 /*block*/); if (write(client_sd, buf, 1) != -1) { test_fail("write() should have failed"); } else if (errno != EAGAIN) { test_fail("write() should have yielded EAGAIN"); } exit(errct); case -1: test_fail("can't fork"); default: break; } close(server_sd); if (send(client_sd, buf, sizeof(buf), 0) != sizeof(buf)) test_fail("send() should have succeded"); check_select(client_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/); if (wait(&status) <= 0) test_fail("wait() should have succeeded"); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) test_fail("child process failed the test"); check_select(client_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/); close(client_sd); sigaction(SIGALRM, &oact, NULL); info->callback_cleanup(); debug("leaving test_intr()"); } /* * Verify that closing a connecting socket before it is accepted will result in * no activity on the accepting side later. */ void test_connect_close(const struct socket_test_info *info) { int server_sd, client_sd; struct sockaddr_storage addr; socklen_t len; debug("entering test_connect_close()"); SOCKET(server_sd, info->domain, info->type, 0); if (bind(server_sd, info->serveraddr, info->serveraddrlen) == -1) test_fail("bind() should have worked"); if (listen(server_sd, 8) == -1) test_fail("listen() should have worked"); fcntl(server_sd, F_SETFL, fcntl(server_sd, F_GETFL) | O_NONBLOCK); check_select(server_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/); SOCKET(client_sd, info->domain, info->type, 0); fcntl(client_sd, F_SETFL, fcntl(client_sd, F_GETFL) | O_NONBLOCK); if (connect(client_sd, info->clientaddr, info->clientaddrlen) != -1 || errno != EINPROGRESS) test_fail("connect() should have yielded EINPROGRESS"); check_select_cond(client_sd, 0 /*read*/, 0 /*write*/, 0 /*block*/, !info->ignore_select_delay); check_select_cond(server_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/, !info->ignore_select_delay); close(client_sd); check_select_cond(server_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/, !info->ignore_select_delay); len = sizeof(addr); errno = 0; if (accept(server_sd, (struct sockaddr *) &addr, &len) != -1) { if (!info->ignore_accept_delay) { test_fail("accept() should have failed"); } } else if (errno != EAGAIN) { test_fail("accept() should have yielded EAGAIN"); } close(server_sd); info->callback_cleanup(); debug("leaving test_connect_close()"); } /* * Verify that closing a listening socket will cause a blocking connect to fail * with ECONNRESET, and that a subsequent write will yield EPIPE. */ void test_listen_close(const struct socket_test_info *info) { int server_sd, client_sd; int status; char byte; debug("entering test_listen_close()"); SOCKET(server_sd, info->domain, info->type, 0); if (bind(server_sd, info->serveraddr, info->serveraddrlen) == -1) test_fail("bind() should have worked"); if (listen(server_sd, 8) == -1) test_fail("listen() should have worked"); switch (fork()) { case 0: sleep(1); exit(0); case -1: test_fail("can't fork"); default: break; } close(server_sd); SOCKET(client_sd, info->domain, info->type, 0); byte = 0; if (write(client_sd, &byte, 1) != -1 || errno != ENOTCONN) /* Yes, you fucked up the fix for the FIXME below. */ test_fail("write() should have yielded ENOTCONN"); if (connect(client_sd, info->clientaddr, info->clientaddrlen) != -1) { if (!info->bug_connect_after_close) { test_fail("connect() should have failed"); } } else if (errno != ECONNRESET) { test_fail("connect() should have yielded ECONNRESET"); } /* * FIXME: currently UDS cannot distinguish between sockets that have * not yet been connected, and sockets that have been disconnected. * Thus, we get the same error for both: ENOTCONN instead of EPIPE. */ #if 0 if (write(client_sd, &byte, 1) != -1 || errno != EPIPE) test_fail("write() should have yielded EPIPE"); #endif close(client_sd); if (wait(&status) <= 0) test_fail("wait() should have succeeded"); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) test_fail("child process failed the test"); info->callback_cleanup(); debug("leaving test_listen_close()"); } /* * Verify that closing a listening socket will cause a nonblocking connect to * result in the socket becoming readable and writable, and yielding ECONNRESET * and EPIPE on the next two writes, respectively. */ void test_listen_close_nb(const struct socket_test_info *info) { int server_sd, client_sd; int status; char byte; debug("entering test_listen_close_nb()"); SOCKET(server_sd, info->domain, info->type, 0); if (bind(server_sd, info->serveraddr, info->serveraddrlen) == -1) test_fail("bind() should have worked"); if (listen(server_sd, 8) == -1) test_fail("listen() should have worked"); switch (fork()) { case 0: sleep(1); exit(0); case -1: test_fail("can't fork"); default: break; } close(server_sd); SOCKET(client_sd, info->domain, info->type, 0); fcntl(client_sd, F_SETFL, fcntl(client_sd, F_GETFL) | O_NONBLOCK); if (connect(client_sd, info->clientaddr, info->clientaddrlen) != -1 || errno != EINPROGRESS) test_fail("connect() should have yielded EINPROGRESS"); check_select_cond(client_sd, 0 /*read*/, 0 /*write*/, 0 /*block*/, !info->ignore_select_delay); check_select_cond(client_sd, 1 /*read*/, 1 /*write*/, 1 /*block*/, !info->ignore_select_delay); byte = 0; if (write(client_sd, &byte, 1) != -1) { if (!info->ignore_write_conn_reset) { test_fail("write() should have failed"); } } else if (errno != ECONNRESET) { test_fail("write() should have yielded ECONNRESET"); } /* * FIXME: currently UDS cannot distinguish between sockets that have * not yet been connected, and sockets that have been disconnected. * Thus, we get the same error for both: ENOTCONN instead of EPIPE. */ #if 0 if (write(client_sd, &byte, 1) != -1 || errno != EPIPE) test_fail("write() should have yielded EPIPE"); #endif check_select_cond(client_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/, !info->ignore_select_delay); close(client_sd); if (wait(&status) <= 0) test_fail("wait() should have succeeded"); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) test_fail("child process failed the test"); info->callback_cleanup(); debug("leaving test_listen_close_nb()"); }