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.)
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.)
Share Improve this question edited Feb 18 at 0:08 Runxi Yu asked Feb 17 at 19:17 Runxi YuRunxi Yu 3432 silver badges9 bronze badges 6 | Show 1 more comment1 Answer
Reset to default 0As 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.
accept()
ing them, without waiting to receive anything from the client. That very likely explains why I consistently get anEPIPE
from your example. Since you describe it as a "small chan[c]e" of an error, I presume that the example is not reflective of your real code, but it nevertheless does suggest a family of possible explanations for theEPIPE
s you see in the real code. – John Bollinger Commented Feb 17 at 19:40