forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
226 lines
4.3 KiB
226 lines
4.3 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* Control socket for client/server test execution |
|
* |
|
* Copyright (C) 2017 Red Hat, Inc. |
|
* |
|
* Author: Stefan Hajnoczi <[email protected]> |
|
*/ |
|
|
|
/* 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(); |
|
} |
|
|
|
/* 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; |
|
}
|
|
|