// SPDX-License-Identifier: GPL-2.0-only
/*
* vsock test utilities
*
* Copyright (C) 2017 Red Hat, Inc.
*
* Author: Stefan Hajnoczi <stefanha@redhat.com>
*/
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/sockios.h>
#include "timeout.h"
#include "control.h"
#include "util.h"
#define KALLSYMS_PATH "/proc/kallsyms"
#define KALLSYMS_LINE_LEN 512
/* Install signal handlers */
void init_signals(void)
{
struct sigaction act = {
.sa_handler = sigalrm,
};
sigaction(SIGALRM, &act, NULL);
signal(SIGPIPE, SIG_IGN);
}
static unsigned int parse_uint(const char *str, const char *err_str)
{
char *endptr = NULL;
unsigned long n;
errno = 0;
n = strtoul(str, &endptr, 10);
if (errno || *endptr != '\0') {
fprintf(stderr, "malformed %s \"%s\"\n", err_str, str);
exit(EXIT_FAILURE);
}
return n;
}
/* Parse a CID in string representation */
unsigned int parse_cid(const char *str)
{
return parse_uint(str, "CID");
}
/* Parse a port in string representation */
unsigned int parse_port(const char *str)
{
return parse_uint(str, "port");
}
/* Wait for the remote to close the connection */
void vsock_wait_remote_close(int fd)
{
struct epoll_event ev;
int epollfd, nfds;
epollfd = epoll_create1(0);
if (epollfd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
ev.events = EPOLLRDHUP | EPOLLHUP;
ev.data.fd = fd;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
perror("epoll_ctl");
exit(EXIT_FAILURE);
}
nfds = epoll_wait(epollfd, &ev, 1, TIMEOUT * 1000);
if (nfds == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
if (nfds == 0) {
fprintf(stderr, "epoll_wait timed out\n");
exit(EXIT_FAILURE);
}
assert(nfds == 1);
assert(ev.events & (EPOLLRDHUP | EPOLLHUP));
assert(ev.data.fd == fd);
close(epollfd);
}
/* Wait until ioctl gives an expected int value.
* Return false if the op is not supported.
*/
bool vsock_ioctl_int(int fd, unsigned long op, int expected)
{
int actual, ret;
char name[32];
snprintf(name, sizeof(name), "ioctl(%lu)", op);
timeout_begin(TIMEOUT);
do {
ret = ioctl(fd, op, &actual);
if (ret < 0) {
if (errno == EOPNOTSUPP || errno == ENOTTY)
break;
perror(name);
exit(EXIT_FAILURE);
}
timeout_check(name);
} while (actual != expected);
timeout_end();
return ret >= 0;
}
/* Wait until transport reports no data left to be sent.
* Return false if transport does not implement the unsent_bytes() callback.
*/
bool vsock_wait_sent(int fd)
{
return vsock_ioctl_int(fd, SIOCOUTQ, 0);
}
/* Create socket <type>, bind to <cid, port>.
* Return the file descriptor, or -1 on error.
*/
int vsock_bind_try(unsigned int cid, unsigned int port, int type)
{
struct sockaddr_vm sa = {
.svm_family = AF_VSOCK,
.svm_cid = cid,
.svm_port = port,
};
int fd, saved_errno;
fd = socket(AF_VSOCK, type, 0);
if (fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
if (bind(fd, (struct sockaddr *)&sa, sizeof(sa))) {
saved_errno = errno;
close(fd);
errno = saved_errno;
fd = -1;
}
return fd;
}
/* Create socket <type>, bind to <cid, port> and return the file descriptor. */
int vsock_bind(unsigned int cid, unsigned int port, int type)
{
int fd;
fd = vsock_bind_try(cid, port