// SPDX-License-Identifier: GPL-2.0-only /* Control socket for client/server test execution * * Copyright (C) 2017 Red Hat, Inc. * * Author: Stefan Hajnoczi <stefanha@redhat.com> */ /* The client and server may need to coordinate to avoid race conditions like * the client attempting to connect to a socket that the server is not * listening on yet. The control socket offers a communications channel for * such coordination tasks. * * If the client calls control_expectln("LISTENING"), then it will block until * the server calls control_writeln("LISTENING"). This provides a simple * mechanism for coordinating between the client and the server. */ #include <errno.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include "timeout.h" #include "control.h" static int control_fd = -1; /* Open the control socket, either in server or client mode */ void control_init(const char *control_host, const char *control_port, bool server) { struct addrinfo hints = { .ai_socktype = SOCK_STREAM, }; struct addrinfo *result = NULL; struct addrinfo *ai; int ret; ret = getaddrinfo(control_host, control_port, &hints, &result); if (ret != 0) { fprintf(stderr, "%s\n", gai_strerror(ret)); exit(EXIT_FAILURE); } for (ai = result; ai; ai = ai->ai_next) { int fd; int val = 1; fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (fd < 0) continue; if (!server) { if (connect(fd, ai->ai_addr, ai->ai_addrlen) < 0) goto next; control_fd = fd; printf("Control socket connected to %s:%s.\n", control_host, control_port); break; } if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) { perror("setsockopt"); exit(EXIT_FAILURE); } if (bind(fd, ai->ai_addr, ai->ai_addrlen) < 0) goto next; if (listen(fd, 1) < 0) goto next; printf("Control socket listening on %s:%s\n", control_host, control_port); fflush(stdout); control_fd = accept(fd, NULL, 0); close(fd); if (control_fd < 0) { perror("accept"); exit(EXIT_FAILURE); } printf("Control socket connection accepted...\n"); break; next: close(fd); } if (control_fd < 0) { fprintf(stderr, "Control socket initialization failed. Invalid address %s:%s?\n", control_host, control_port); exit(EXIT_FAILURE); } freeaddrinfo(result); } /* Free resources */ void control_cleanup(void) { close(control_fd); control_fd = -1; } /* Write a line to the control socket */ void control_writeln(const char *str) { ssize_t len = strlen(str); ssize_t ret; timeout_begin(TIMEOUT); do { ret = send(control_fd, str, len, MSG_MORE); timeout_check("send"); } while (ret < 0 && errno == EINTR); if (ret != len) { perror("send"); exit(EXIT_FAILURE); } do { ret = send(control_fd, "\n", 1, 0); timeout_check("send"); } while (ret < 0 && errno == EINTR); if (ret != 1) { perror("send"); exit(EXIT_FAILURE); } timeout_end(); } void control_writeulong(unsigned long value) { char str[32]; if (snprintf(str, sizeof(str), "%lu", value) >= sizeof(str)) { perror("snprintf"); exit(EXIT_FAILURE); } control_writeln(str); } unsigned long control_readulong(void) { unsigned long value; char *str; str = control_readln(); if (!str) exit(EXIT_FAILURE); value = strtoul(str, NULL, 10); free(str); return value; } /* Return the next line from the control socket (without the trailing newline). * * The program terminates if a timeout occurs. * * The caller must free() the returned string. */ char *control_readln(void) { char *buf = NULL; size_t idx = 0; size_t buflen = 0; timeout_begin(TIMEOUT); for (;;) { ssize_t ret; if (idx >= buflen) { char *new_buf; new_buf = realloc(buf, buflen + 80); if (!new_buf) { perror("realloc"); exit(EXIT_FAILURE); } buf = new_buf; buflen += 80; } do { ret = recv(control_fd, &buf[idx], 1, 0); timeout_check("recv"); } while (ret < 0 && errno == EINTR); if (ret == 0) { fprintf(stderr, "unexpected EOF on control socket\n"); exit(EXIT_FAILURE); } if (ret != 1) { perror("recv"); exit(EXIT_FAILURE); } if (buf[idx] == '\n') { buf[idx] = '\0'; break; } idx++; } timeout_end(); return buf; } /* Wait until a given line is received or a timeout occurs */ void control_expectln(const char *str) { char *line; line = control_readln(); control_cmpln(line, str, true); free(line); } bool control_cmpln(char *line, const char *str, bool fail) { if (strcmp(str, line) == 0) return true; if (fail) { fprintf(stderr, "expected \"%s\" on control socket, got \"%s\"\n", str, line); exit(EXIT_FAILURE); } return false; }