With the following, the client has a small chance of receiving "Broken pipe" when it attempts to splice, despite the socket being, as far as I can see, open. (The "small chance" seems to depends on various factors, including but not limited to system load.)
I'm not sure why this is the case. If I remember correctly, EPIPE is given when writing to a file descriptor with no listeners. It's even more confusing how it happens intermittently.
A script to reproduce how I build and run the programs, is attached.
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#define SOCKET_PATH "test.sock"
int main(void)
{
int server_fd, client_fd;
struct sockaddr_un addr;
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
if (bind
(server_fd, (struct sockaddr *)&addr,
sizeof(struct sockaddr_un)) == -1) {
perror("bind");
close(server_fd);
exit(EXIT_FAILURE);
}
if (listen(server_fd, 1) == -1) {
perror("listen");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Listening %s\n", SOCKET_PATH);
while (1) {
client_fd = accept(server_fd, NULL, NULL);
if (client_fd == -1) {
perror("accept");
close(server_fd);
exit(EXIT_FAILURE);
}
close(client_fd);
}
close(server_fd);
return 0;
}
client.c
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#define SOCKET_PATH "test.sock"
int main(void)
{
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
perror("signal");
return EXIT_FAILURE;
}
struct stat stdin_stat;
if (fstat(STDIN_FILENO, &stdin_stat) == -1) {
perror("fstat on stdin");
return EXIT_FAILURE;
}
if (!S_ISFIFO(stdin_stat.st_mode)) {
dprintf(STDERR_FILENO, "stdin must be a pipe\n");
return EXIT_FAILURE;
}
int stdin_pipe_size = fcntl(STDIN_FILENO, F_GETPIPE_SZ);
if (stdin_pipe_size == -1) {
perror("fcntl on stdin");
return EXIT_FAILURE;
}
int sock;
struct sockaddr_un addr;
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock == -1) {
perror("internal socket creation");
return EXIT_FAILURE;
}
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
if (connect(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_un))
== -1) {
perror("internal socket connect");
close(sock);
return EXIT_FAILURE;
}
ssize_t stdin_bytes_spliced;
while ((stdin_bytes_spliced =
splice(STDIN_FILENO, NULL, sock, NULL, stdin_pipe_size,
SPLICE_F_MORE)) > 0) {
}
if (stdin_bytes_spliced == -1) {
perror("splice stdin to internal socket");
close(sock);
return EXIT_FAILURE;
}
close(sock);
return EXIT_SUCCESS;
}
Running:
#!/bin/bash
rm -f test.sock
cc -Wall -Wextra -Werror -pedantic -std=c99 -D_GNU_SOURCE server.c -o server
cc -Wall -Wextra -Werror -pedantic -std=c99 -D_GNU_SOURCE client.c -o client
./server &
server_pid="$!"
sleep 1 # give it time to start listening
counter=0
while true
do
echo "$counter"
head -c 512 /dev/zero | ./client || break
sleep 0.04
counter=$((counter + 1))
done
kill $server_pid
(My "real problem" occurs in this Go code, but since sharing that would be difficult to reproduce, I decided to come up with a minimal working example in C, and verified that the erroneous behavior is the same across the MWE and the project's actual code.)
As the comments suggest, the problem is that the server prematurely closes the connection before reading anything out of it. (If it wasn't for the SIGPIPE handler, my program would terminate right away.)
My solution is to call shutdown(sock, SHUT_WR)
on the client side after the splicing, so that the server side can read until EOF to obtain all of the standard input of the client. The server side shall only close the connection after it has done reading to EOF.