diff options
Diffstat (limited to 'test/common/child.c')
-rw-r--r-- | test/common/child.c | 454 |
1 files changed, 454 insertions, 0 deletions
diff --git a/test/common/child.c b/test/common/child.c new file mode 100644 index 0000000..84f580e --- /dev/null +++ b/test/common/child.c @@ -0,0 +1,454 @@ +/* + Framework for testing with a server process + Copyright (C) 2001-2002, Joe Orton <joe@manyfish.co.uk> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "config.h" + +#include <sys/types.h> + +#include <sys/wait.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <sys/stat.h> + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include <fcntl.h> +#include <netdb.h> +#include <signal.h> + +#include "ne_socket.h" +#include "ne_utils.h" +#include "ne_string.h" + +#include "tests.h" +#include "child.h" + +static pid_t child = 0; + +int clength; + +static struct in_addr lh_addr = {0}, hn_addr = {0}; + +const char *want_header = NULL; +got_header_fn got_header = NULL; +char *local_hostname = NULL; + +/* If we have pipe(), then use a pipe between the parent and child to + * know when the child is ready to accept incoming connections. + * Otherwise use boring sleep()s trying to avoid the race condition + * between listen() and connect() in the two processes. */ +#ifdef HAVE_PIPE +#define USE_PIPE 1 +#endif + +int lookup_localhost(void) +{ + /* this will break if a system is set up so that `localhost' does + * not resolve to 127.0.0.1, but... */ + lh_addr.s_addr = inet_addr("127.0.0.1"); + return OK; +} + +int lookup_hostname(void) +{ + char buf[BUFSIZ]; + struct hostent *ent; + + local_hostname = NULL; + ONV(gethostname(buf, BUFSIZ) < 0, + ("gethostname failed: %s", strerror(errno))); + + ent = gethostbyname(buf); +#ifdef HAVE_HSTRERROR + ONV(ent == NULL, + ("could not resolve `%s': %s", buf, hstrerror(h_errno))); +#else + ONV(ent == NULL, ("could not resolve `%s'", buf)); +#endif + + local_hostname = ne_strdup(ent->h_name); + + return OK; +} + +static int do_listen(struct in_addr addr, int port) +{ + int ls = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in saddr = {0}; + int val = 1; + + setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, (void *)&val, sizeof(int)); + + saddr.sin_addr = addr; + saddr.sin_port = htons(port); + saddr.sin_family = AF_INET; + + if (bind(ls, (struct sockaddr *)&saddr, sizeof(saddr))) { + printf("bind failed: %s\n", strerror(errno)); + return -1; + } + if (listen(ls, 5)) { + printf("listen failed: %s\n", strerror(errno)); + return -1; + } + + return ls; +} + +void minisleep(void) +{ +#ifdef HAVE_USLEEP + usleep(500); +#else + sleep(1); +#endif +} + +/* This runs as the child process. */ +static int server_child(int readyfd, struct in_addr addr, int port, + server_fn callback, void *userdata) +{ + ne_socket *s = ne_sock_create(); + int ret, listener; + + in_child(); + + listener = do_listen(addr, port); + if (listener < 0) + return FAIL; + +#ifdef USE_PIPE + /* Tell the parent we're ready for the request. */ + write(readyfd, "a", 1); +#endif + + ONN("accept failed", ne_sock_accept(s, listener)); + + ret = callback(s, userdata); + + ne_sock_close(s); + + return ret; +} + +int spawn_server(int port, server_fn fn, void *ud) +{ + return spawn_server_addr(1, port, fn, ud); +} + +int spawn_server_addr(int bind_local, int port, server_fn fn, void *ud) +{ + int fds[2]; + struct in_addr addr; + + addr = bind_local?lh_addr:hn_addr; + +#ifdef USE_PIPE + if (pipe(fds)) { + perror("spawn_server: pipe"); + return FAIL; + } +#else + /* avoid using uninitialized variable. */ + fds[0] = fds[1] = 0; +#endif + + child = fork(); + + ONN("fork server", child == -1); + + if (child == 0) { + /* this is the child. */ + int ret; + + ret = server_child(fds[1], addr, port, fn, ud); + +#ifdef USE_PIPE + close(fds[0]); + close(fds[1]); +#endif + + /* print the error out otherwise it gets lost. */ + if (ret) { + printf("server child failed: %s\n", test_context); + } + + /* and quit the child. */ + exit(ret); + } else { + char ch; + +#ifdef USE_PIPE + if (read(fds[0], &ch, 1) < 0) + perror("parent read"); + + close(fds[0]); + close(fds[1]); +#else + minisleep(); +#endif + + return OK; + } +} + +int spawn_server_repeat(int port, server_fn fn, void *userdata, int n) +{ + int fds[2]; + +#ifdef USE_PIPE + if (pipe(fds)) { + perror("spawn_server: pipe"); + return FAIL; + } +#else + /* avoid using uninitialized variable. */ + fds[0] = fds[1] = 0; +#endif + + child = fork(); + + if (child == 0) { + /* this is the child. */ + int listener, count = 0; + + in_child(); + + listener = do_listen(lh_addr, port); + +#ifdef USE_PIPE + write(fds[1], "Z", 1); +#endif + + close(fds[1]); + close(fds[0]); + + /* Loop serving requests. */ + while (++count < n) { + ne_socket *sock = ne_sock_create(); + int ret; + + NE_DEBUG(NE_DBG_HTTP, "child awaiting connection #%d.\n", count); + ONN("accept failed", ne_sock_accept(sock, listener)); + ret = fn(sock, userdata); + ne_sock_close(sock); + NE_DEBUG(NE_DBG_HTTP, "child served request, %d.\n", ret); + if (ret) { + printf("server child failed: %s\n", test_context); + exit(-1); + } + /* don't send back notification to parent more than + * once. */ + } + + NE_DEBUG(NE_DBG_HTTP, "child aborted.\n"); + close(listener); + + exit(-1); + + } else { + char ch; + /* this is the parent. wait for the child to get ready */ +#ifdef USE_PIPE + if (read(fds[0], &ch, 1) < 0) + perror("parent read"); + + close(fds[0]); + close(fds[1]); +#else + minisleep(); +#endif + } + + return OK; +} + +int dead_server(void) +{ + int status; + + if (waitpid(child, &status, WNOHANG)) { + /* child quit already! */ + return FAIL; + } + + NE_DEBUG(NE_DBG_HTTP, "child has not quit.\n"); + + return OK; +} + + +int await_server(void) +{ + int status; + + (void) wait(&status); + + /* so that we aren't reaped by mistake. */ + child = 0; + + ONN("error from server process", WEXITSTATUS(status)); + + return OK; +} + +int reap_server(void) +{ + int status; + + if (child != 0) { + (void) kill(child, SIGTERM); + minisleep(); + (void) wait(&status); + child = 0; + } + + return OK; +} + +ssize_t server_send(ne_socket *sock, const char *str, size_t len) +{ + NE_DEBUG(NE_DBG_HTTP, "Sending: %.*s\n", (int)len, str); + return ne_sock_fullwrite(sock, str, len); +} + +int discard_request(ne_socket *sock) +{ + char buffer[1024]; + size_t offset = want_header?strlen(want_header):0; + + clength = 0; + + NE_DEBUG(NE_DBG_HTTP, "Discarding request...\n"); + do { + ONV(ne_sock_readline(sock, buffer, 1024) < 0, + ("error reading line: %s", ne_sock_error(sock))); + NE_DEBUG(NE_DBG_HTTP, "[req] %s", buffer); + if (strncasecmp(buffer, "content-length:", 15) == 0) { + clength = atoi(buffer + 16); + } + if (got_header != NULL && want_header != NULL && + strncasecmp(buffer, want_header, offset) == 0 && + buffer[offset] == ':') { + char *value = buffer + offset + 1; + if (*value == ' ') value++; + got_header(ne_shave(value, "\r\n")); + } + } while (strcmp(buffer, "\r\n") != 0); + + return OK; +} + +int discard_body(ne_socket *sock) +{ + while (clength > 0) { + char buf[BUFSIZ]; + size_t bytes = clength; + ssize_t ret; + if (bytes > sizeof(buf)) bytes = sizeof(buf); + NE_DEBUG(NE_DBG_HTTP, "Discarding %" NE_FMT_SIZE_T " bytes.\n", + bytes); + ret = ne_sock_read(sock, buf, bytes); + ONV(ret < 0, ("socket read failed (%" NE_FMT_SSIZE_T "): %s", + ret, ne_sock_error(sock))); + clength -= ret; + NE_DEBUG(NE_DBG_HTTP, "Got %" NE_FMT_SSIZE_T " bytes.\n", ret); + } + NE_DEBUG(NE_DBG_HTTP, "Discard successful.\n"); + return OK; +} + +int serve_file(ne_socket *sock, void *ud) +{ + char buffer[BUFSIZ]; + struct stat st; + struct serve_file_args *args = ud; + ssize_t ret; + int fd; + + CALL(discard_request(sock)); + + ne_sock_fullread(sock, buffer, clength); + + fd = open(args->fname, O_RDONLY); + if (fd < 0) { + SEND_STRING(sock, + "HTTP/1.0 404 File Not Found\r\n" + "Content-Length: 0\r\n\r\n"); + return 0; + } + + ONN("fstat fd", fstat(fd, &st)); + + SEND_STRING(sock, "HTTP/1.0 200 OK\r\n"); + if (args->chunks) { + sprintf(buffer, "Transfer-Encoding: chunked\r\n"); + } else { + sprintf(buffer, "Content-Length: %" NE_FMT_OFF_T "\r\n", + st.st_size); + } + + if (args->headers) { + strcat(buffer, args->headers); + } + + strcat(buffer, "\r\n"); + + SEND_STRING(sock, buffer); + + NE_DEBUG(NE_DBG_HTTP, "Serving %s (%" NE_FMT_OFF_T " bytes).\n", + args->fname, st.st_size); + + if (args->chunks) { + char buf[1024]; + + while ((ret = read(fd, &buf, args->chunks)) > 0) { + /* this is a small integer, cast it explicitly to avoid + * warnings with printing an ssize_t. */ + sprintf(buffer, "%x\r\n", (unsigned int)ret); + SEND_STRING(sock, buffer); + ONN("writing body", ne_sock_fullwrite(sock, buf, ret)); + SEND_STRING(sock, "\r\n"); + } + + SEND_STRING(sock, "0\r\n\r\n"); + + } else { + while ((ret = read(fd, buffer, BUFSIZ)) > 0) { + ONN("writing body", ne_sock_fullwrite(sock, buffer, ret)); + } + } + + ONN("error reading from file", ret < 0); + + (void) close(fd); + + return OK; +} |