blob: 9aab2d29d479f9b0f8ab36df9680d4da3d7c658e [file] [edit]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* TCP send client. Pass -s to use splice/sendfile; -z to use MSG_ZEROCOPY.
*
* Copyright (C) 2023 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <getopt.h>
#include <limits.h>
#include <fcntl.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#define OSERROR(X, Y) do { if ((long)(X) == -1) { perror(Y); exit(1); } } while(0)
static unsigned char buffer[4096] __attribute__((aligned(4096)));
static __attribute__((noreturn))
void format(void)
{
fprintf(stderr, "tcp-send [-46sz][-p<port>][-n<size>] <file>|- <server>\n");
exit(2);
}
int main(int argc, char *argv[])
{
struct addrinfo *addrs = NULL, hints = {};
struct stat st;
const char *filename, *sockname, *service = "5555";
ssize_t r, w, o;
size_t size = LONG_MAX;
char *end;
bool use_sendfile = false, use_zerocopy = false, all = true;
int opt, sock, fd = 0, gai;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
while ((opt = getopt(argc, argv, "46n:p:sz")) != EOF) {
switch (opt) {
case '4':
hints.ai_family = AF_INET;
break;
case '6':
hints.ai_family = AF_INET6;
break;
case 'n':
size = strtoul(optarg, &end, 0);
switch (*end) {
case 'K':
case 'k':
size *= 1024;
break;
case 'M':
case 'm':
size *= 1024 * 1024;
break;
}
all = false;
break;
case 'p':
service = optarg;
break;
case 's':
use_sendfile = true;
break;
case 'z':
use_zerocopy = true;
break;
default:
format();
}
}
argc -= optind;
argv += optind;
if (argc != 2)
format();
filename = argv[0];
sockname = argv[1];
gai = getaddrinfo(sockname, service, &hints, &addrs);
if (gai) {
fprintf(stderr, "%s: %s\n", sockname, gai_strerror(gai));
exit(3);
}
if (!addrs) {
fprintf(stderr, "%s: No addresses\n", sockname);
exit(3);
}
sockname = addrs->ai_canonname;
sock = socket(addrs->ai_family, addrs->ai_socktype, addrs->ai_protocol);
OSERROR(sock, "socket");
OSERROR(connect(sock, addrs->ai_addr, addrs->ai_addrlen), "connect");
if (strcmp(filename, "-") != 0) {
fd = open(filename, O_RDONLY);
OSERROR(fd, filename);
OSERROR(fstat(fd, &st), filename);
if (size > st.st_size)
size = st.st_size;
} else {
OSERROR(fstat(fd, &st), filename);
}
if (!use_sendfile) {
unsigned int flags = 0;
if (use_zerocopy) {
int zcflag = 1;
OSERROR(setsockopt(sock, SOL_SOCKET, SO_ZEROCOPY,
&zcflag, sizeof(zcflag)),
"SOCK_ZEROCOPY");
flags |= MSG_ZEROCOPY;
}
while (size) {
r = read(fd, buffer, sizeof(buffer));
OSERROR(r, filename);
if (r == 0)
break;
size -= r;
o = 0;
do {
flags &= ~MSG_MORE;
if (size > 0)
flags |= MSG_MORE;
w = send(sock, buffer + o, r - o, flags);
OSERROR(w, "sock/send");
o += w;
} while (o < r);
}
if (flags & MSG_MORE)
send(sock, NULL, 0, flags & ~MSG_MORE);
} else if (S_ISFIFO(st.st_mode)) {
do {
r = splice(fd, NULL, sock, NULL, size,
size > 0 ? SPLICE_F_MORE : 0);
OSERROR(r, "sock/splice");
size -= r;
} while (r > 0 && size > 0);
if (size && !all) {
fprintf(stderr, "Short splice\n");
exit(1);
}
} else {
r = sendfile(sock, fd, NULL, size);
OSERROR(r, "sock/sendfile");
if (r != size) {
fprintf(stderr, "Short sendfile\n");
exit(1);
}
}
OSERROR(close(sock), "sock/close");
OSERROR(close(fd), "close");
return 0;
}