From 9c99b3b3b995d17cf8f9023156b8c3f63c6bccb8 Mon Sep 17 00:00:00 2001 From: Thomas Veerman Date: Tue, 26 Feb 2013 11:30:44 +0000 Subject: [PATCH] test67: test opening files and sockets with O_CLOEXEC Change-Id: If71e61f830c6d3b5154368e189baa4f70bd73850 --- test/Makefile | 6 +- test/run | 2 +- test/t67a.c | 33 ++++ test/t67b.c | 39 +++++ test/test67.c | 409 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 485 insertions(+), 4 deletions(-) create mode 100644 test/t67a.c create mode 100644 test/t67b.c create mode 100644 test/test67.c diff --git a/test/Makefile b/test/Makefile index 8f53897c4..bdb06e088 100644 --- a/test/Makefile +++ b/test/Makefile @@ -23,11 +23,11 @@ OBJS.test57=test57loop.o 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 64 65 66 +61 62 64 65 66 67 PROG+= test$(t) .endfor -PROG+= t10a t11a t11b t40a t40b t40c t40d t40e t40f t60a t60b +PROG+= t10a t11a t11b t40a t40b t40c t40d t40e t40f t60a t60b t67a t67b .include @@ -53,5 +53,5 @@ clean: .PHONY .MAKE $(MAKE) -C select clean rm -rf *.o *.s *.bak test? test?? t10a t11a t11b \ t40a t40b t40c t40d t40e t40f \ - t60a t60b \ + t60a t60b t67a t67b \ DIR* diff --git a/test/run b/test/run index 7b5c621b5..df2ca9912 100755 --- a/test/run +++ b/test/run @@ -14,7 +14,7 @@ badones= # list of tests that failed tests=" 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 \ + 61 62 63 64 65 66 67\ sh1.sh sh2.sh interp.sh" tests_no=`expr 0` diff --git a/test/t67a.c b/test/t67a.c new file mode 100644 index 000000000..d39835856 --- /dev/null +++ b/test/t67a.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include +#include + +int +main(int argc, char *argv[]) +{ + int fd, fd_parent; + char buf[1]; + + if (argc != 2) { + return 1; + } + + fd_parent = atoi(argv[1]); + + /* If we open a new file, the fd we obtain should be fd_parent + 1 */ + fd = open("open_plusplus_fd", O_CREAT|O_RDWR, 0660); + if (fd != fd_parent + 1) { + return 2; + } + + /* Also, writing to fd_parent should succeed */ + if (write(fd_parent, buf, sizeof(buf)) <= 0) { + return 3; + } + + return 0; +} + diff --git a/test/t67b.c b/test/t67b.c new file mode 100644 index 000000000..ccdebd5ce --- /dev/null +++ b/test/t67b.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include +#include +#include + +int +main(int argc, char *argv[]) +{ + int fd, fd_parent; + char buf[1]; + + if (argc != 2) { + return 1; + } + + fd_parent = atoi(argv[1]); + + /* Writing to fd_parent should fail as it has to be closed at this + * point */ + if (write(fd_parent, buf, sizeof(buf)) != -1) { + return 2; + } + if (errno != EBADF) { + return 3; + } + + /* If we open a new file, the fd we obtain should be identical to + * fd_parent */ + fd = open("open_identical_fd", O_CREAT|O_RDWR, 0660); + if (fd != fd_parent) { + return 4; + } + + return 0; +} + diff --git a/test/test67.c b/test/test67.c new file mode 100644 index 000000000..7f79b397b --- /dev/null +++ b/test/test67.c @@ -0,0 +1,409 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_ERROR 5 +#define CLOEXEC_PORT 3490 +#define FORK_PORT 3491 +#include "common.c" + +static int fd = 0; + +void copy_subtests(void); +void test_open_file_cloexec(void); +void test_open_file_fork(void); +void test_open_socket_cloexec(void); +void test_open_socket_fork(void); +void start_socket_server(int port); +int start_socket_client(int port, int flag); + +void +copy_subtests() +{ + char *subtests[] = { "t67a", "t67b" }; + char copy_cmd[8 + PATH_MAX + 1]; + int i, no_tests; + + no_tests = sizeof(subtests) / sizeof(char *); + + for (i = 0; i < no_tests; i++) { + snprintf(copy_cmd, 8 + PATH_MAX, "cp ../%s .", subtests[i]); + system(copy_cmd); + } +} + +void +test_open_file_cloexec() +{ + int flags; + pid_t pid; + + /* Let's create a file with O_CLOEXEC turned on */ + fd = open("file", O_RDWR|O_CREAT|O_CLOEXEC, 0660); + if (fd < 0) e(1); + + /* Now verify through fcntl the flag is indeed set */ + flags = fcntl(fd, F_GETFD); + if (flags < 0) e(2); + if (!(flags & FD_CLOEXEC)) e(3); + + /* Fork a child and let child exec a test program that verifies + * fd is not a valid file */ + pid = fork(); + if (pid == -1) e(4); + else if (pid == 0) { + /* We're the child */ + char fd_buf[2]; + + /* Verify again O_CLOEXEC is on */ + flags = fcntl(fd, F_GETFD); + if (flags < 0) e(5); + if (!(flags & FD_CLOEXEC)) e(6); + + snprintf(fd_buf, sizeof(fd_buf), "%d", fd); + execl("./t67b", "t67b", fd_buf, NULL); + + /* Should not reach this */ + exit(1); + } else { + /* We're the parent */ + int result; + + if (waitpid(pid, &result, 0) == -1) e(7); + if (WEXITSTATUS(result) != 0) e(8); + } + close(fd); +} + +void +test_open_file_fork() +{ + int flags; + pid_t pid; + + /* Let's create a file with O_CLOEXEC NOT turned on */ + fd = open("file", O_RDWR|O_CREAT, 0660); + if (fd < 0) e(1); + + /* Now verify through fcntl the flag is indeed not set */ + flags = fcntl(fd, F_GETFD); + if (flags < 0) e(2); + if (flags & FD_CLOEXEC) e(3); + + /* Fork a child and let child exec a test program that verifies + * fd is a valid file */ + pid = fork(); + if (pid == -1) e(4); + else if (pid == 0) { + /* We're the child */ + char fd_buf[2]; + + /* Verify again O_CLOEXEC is off */ + flags = fcntl(fd, F_GETFD); + if (flags < 0) e(5); + if (flags & FD_CLOEXEC) e(6); + + snprintf(fd_buf, sizeof(fd_buf), "%d", fd); + execl("./t67a", "t67a", fd_buf, NULL); + + /* Should not reach this */ + exit(1); + } else { + /* We're the parent */ + int result = 0; + + if (waitpid(pid, &result, 0) == -1) e(7); + if (WEXITSTATUS(result) != 0) e(8); + } + close(fd); +} + +int +start_socket_client(int port, int flag) +{ + int fd_sock; + struct hostent *he; + struct sockaddr_in server; + + if ((fd_sock = socket(PF_INET, SOCK_STREAM|flag, 0)) < 0) { + perror("Error obtaining socket\n"); + e(1); + } + + if ((he = gethostbyname("127.0.0.1")) == NULL) { + perror("Error retrieving home\n"); + e(2); + } + + /* Copy server host result */ + memcpy(&server.sin_addr, he->h_addr_list[0], he->h_length); + server.sin_family = AF_INET; + server.sin_port = htons(port); + + /* Normally, we'd zerofill sin_zero, but there is no such thing on + * Minix at the moment */ +#if !defined(__minix) + memset(&server.sin_zero, '\0', sizeof(server.sin_zero)); +#endif + + if (connect(fd_sock, (struct sockaddr *) &server, sizeof(server)) < 0){ + perror("Error connecting to server\n"); + e(3); + } + + return fd_sock; +} + + +void +start_socket_server(int port) +{ +#if !defined(__minix) + int yes = 1; +#endif + int fd_sock, fd_new, r; + struct sockaddr_in my_addr; + struct sockaddr_in other_addr; + socklen_t other_size; + char buf[1]; + + if ((fd_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) { + perror("Error getting socket\n"); + e(1); + } + + my_addr.sin_family = AF_INET; + my_addr.sin_port = htons(port); + my_addr.sin_addr.s_addr = INADDR_ANY; + /* Normally we'd zerofill sin_zero, but current Minix socket interface + * does not support that field */ +#if !defined(__minix) + memset(&my_addr.sin_zero, '\0', sizeof(sin.sin_zero)); +#endif + + /* Reuse port number when invoking test often */ +#if !defined(__minix) + if (setsockopt(fd_sock, SOL_SOCKET, SO_REUSEADDR, &yes, + sizeof(int)) < 0) { + perror("Error setting port reuse option\n"); + e(2); + } +#endif + + /* Bind to port */ + if (bind(fd_sock, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) { + perror("Error binding to port\n"); + e(3); + } + + /* Set socket in listening mode */ + if (listen(fd_sock, 20) < 0) { + perror("Error listening for incoming connections"); + e(4); + } + + /* Accept incoming connections */ + fd_new = accept(fd_sock, (struct sockaddr *) &other_addr, &other_size); + + if (fd_new < 0) { + perror("Error accepting new connections\n"); + e(5); + } + + r = read(fd_new, buf, sizeof(buf)); + exit(0); +} + +void +test_open_socket_cloexec() +{ +/* This subtest will start a server and client using TCP. The client will + * open the socket with SOCK_CLOEXEC turned on, so that after a fork+exec, the + * socket should become invalid. + * o + * / | + * server | + * (accept) | + * | | \ + * | | client + * | | (connect) + * | | | \ + * | | | client_fork + * | | | (exec t67b) + * (read) | | (write) + * | | | / + * | | (waitpid client_fork) + * \ | | + * (waitpid server) | + * | / + * (waitpid client) + * | + * o + */ + pid_t pid_server, pid_client; + int result; + + pid_server = fork(); + if (pid_server < 0) e(1); + if (pid_server == 0) { + start_socket_server(CLOEXEC_PORT); + return; /* Never reached */ + } + + pid_client = fork(); + if (pid_client < 0) e(2); + if (pid_client == 0) { + pid_t pid_client_fork; + int sockfd; + + sockfd = start_socket_client(CLOEXEC_PORT, SOCK_CLOEXEC); + if (sockfd < 0) e(4); + + pid_client_fork = fork(); + if (pid_client_fork < 0) { + e(5); + exit(5); + } + if (pid_client_fork == 0) { + /* We're a fork of the client. After we exec, the + * socket should become invalid due to the SOCK_CLOEXEC + * flag. + */ + char sockfd_buf[2]; + int flags; + + /* Verify O_CLOEXEC is on */ + flags = fcntl(sockfd, F_GETFD); + if (flags < 0) e(5); + if (!(flags & FD_CLOEXEC)) e(6); + + /* t67b will verify that it can't write to sockfd and + * that opening a new file will yield a file descriptor + * with the same number. + */ + snprintf(sockfd_buf, sizeof(sockfd_buf), "%d", sockfd); + execl("./t67b", "t67b", sockfd_buf, NULL); + + /* Should not reach this */ + exit(1); + } else { + if (waitpid(pid_client_fork, &result, 0) < 0) e(8); + exit(WEXITSTATUS(result)); /* Pass on error to main */ + } + exit(0); /* Never reached */ + } + + if (waitpid(pid_server, &result, 0) < 0) e(3); + if (waitpid(pid_client, &result, 0) < 0) e(4); + + /* Let's inspect client result */ + if (WEXITSTATUS(result) != 0) e(5); +} + +void +test_open_socket_fork(void) +{ +/* This subtest will start a server and client using TCP. The client will + * open the socket with SOCK_CLOEXEC turned off, so that after a fork+exec, the + * socket should stay valid. + * o + * / | + * server | + * (accept) | + * | | \ + * | | client + * | | (connect) + * | | | \ + * | | | client_fork + * | | | (exec t67a) + * (read) | | (write) + * | | | / + * | | (waitpid client_fork) + * \ | | + * (waitpid server) | + * | / + * (waitpid client) + * | + * o + */ + pid_t pid_server, pid_client; + int result; + + pid_server = fork(); + if (pid_server < 0) e(1); + if (pid_server == 0) { + start_socket_server(FORK_PORT); + return; /* Never reached */ + } + + pid_client = fork(); + if (pid_client < 0) e(2); + if (pid_client == 0) { + pid_t pid_client_fork; + int sockfd; + + sockfd = start_socket_client(FORK_PORT, 0); + if (sockfd < 0) e(4); + + pid_client_fork = fork(); + if (pid_client_fork < 0) { + e(5); + exit(5); + } + if (pid_client_fork == 0) { + /* We're a fork of the client. After we exec, the + * socket should stay valid due to lack of SOCK_CLOEXEC + * flag. + */ + char sockfd_buf[2]; + int flags; + + /* Verify O_CLOEXEC is off */ + flags = fcntl(sockfd, F_GETFD); + if (flags < 0) e(5); + if (flags & FD_CLOEXEC) e(6); + + /* t67a will verify that it can't write to sockfd and + * that opening a new file will yield a file descriptor + * with a higher number. + */ + snprintf(sockfd_buf, sizeof(sockfd_buf), "%d", sockfd); + execl("./t67a", "t67a", sockfd_buf, NULL); + + /* Should not reach this */ + exit(1); + } else { + if (waitpid(pid_client_fork, &result, 0) < 0) e(8); + exit(WEXITSTATUS(result)); /* Pass on error to main */ + } + exit(0); /* Never reached */ + } + + if (waitpid(pid_server, &result, 0) < 0) e(3); + if (waitpid(pid_client, &result, 0) < 0) e(4); + + /* Let's inspect client result */ + if (WEXITSTATUS(result) != 0) e(5); +} + +int +main(int argc, char *argv[]) +{ + start(67); + copy_subtests(); + test_open_file_fork(); + test_open_file_cloexec(); + test_open_socket_fork(); + test_open_socket_cloexec(); + quit(); + return(-1); /* Unreachable */ +} +