PhilBot PhilBot - 5 months ago 29
Linux Question

Ubuntu Linux send file descriptor with Unix Domain Socket

I'm trying to send a file descriptor between a socketpair with the code pasted below. This code is from: http://www.thomasstover.com/uds.html. I am running on Ubuntu 16.04 64-bit.

The problem is that the file descriptor received for my run of the program is "3" and not "4". I also cannot read any data from it in the receiving process. Why is it not working?

The console output looks like this:

Parent at work
FILE TO SEND HAS DESCRIPTOR: 4
Parent read: [[hello phil
]]
Child at play
Read 3!
Done: 0 Success!
Parent exits


Code:

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>

int send_fd(int socket, int fd_to_send)
{
struct msghdr socket_message;
struct iovec io_vector[1];
struct cmsghdr *control_message = NULL;
char message_buffer[1];
/* storage space needed for an ancillary element with a paylod of length is CMSG_SPACE(sizeof(length)) */
char ancillary_element_buffer[CMSG_SPACE(sizeof(int))];
int available_ancillary_element_buffer_space;

/* at least one vector of one byte must be sent */
message_buffer[0] = 'F';
io_vector[0].iov_base = message_buffer;
io_vector[0].iov_len = 1;

/* initialize socket message */
memset(&socket_message, 0, sizeof(struct msghdr));
socket_message.msg_iov = io_vector;
socket_message.msg_iovlen = 1;

/* provide space for the ancillary data */
available_ancillary_element_buffer_space = CMSG_SPACE(sizeof(int));
memset(ancillary_element_buffer, 0, available_ancillary_element_buffer_space);
socket_message.msg_control = ancillary_element_buffer;
socket_message.msg_controllen = available_ancillary_element_buffer_space;

/* initialize a single ancillary data element for fd passing */
control_message = CMSG_FIRSTHDR(&socket_message);
control_message->cmsg_level = SOL_SOCKET;
control_message->cmsg_type = SCM_RIGHTS;
control_message->cmsg_len = CMSG_LEN(sizeof(int));
*((int *) CMSG_DATA(control_message)) = fd_to_send;

return sendmsg(socket, &socket_message, 0);
}

int recv_fd(int socket)
{
int sent_fd, available_ancillary_element_buffer_space;
struct msghdr socket_message;
struct iovec io_vector[1];
struct cmsghdr *control_message = NULL;
char message_buffer[1];
char ancillary_element_buffer[CMSG_SPACE(sizeof(int))];

/* start clean */
memset(&socket_message, 0, sizeof(struct msghdr));
memset(ancillary_element_buffer, 0, CMSG_SPACE(sizeof(int)));

/* setup a place to fill in message contents */
io_vector[0].iov_base = message_buffer;
io_vector[0].iov_len = 1;
socket_message.msg_iov = io_vector;
socket_message.msg_iovlen = 1;

/* provide space for the ancillary data */
socket_message.msg_control = ancillary_element_buffer;
socket_message.msg_controllen = CMSG_SPACE(sizeof(int));

if(recvmsg(socket, &socket_message, MSG_CMSG_CLOEXEC) < 0)
return -1;

if(message_buffer[0] != 'F')
{
/* this did not originate from the above function */
return -1;
}

if((socket_message.msg_flags & MSG_CTRUNC) == MSG_CTRUNC)
{
/* we did not provide enough space for the ancillary element array */
return -1;
}

/* iterate ancillary elements */
for(control_message = CMSG_FIRSTHDR(&socket_message);
control_message != NULL;
control_message = CMSG_NXTHDR(&socket_message, control_message))
{
if( (control_message->cmsg_level == SOL_SOCKET) &&
(control_message->cmsg_type == SCM_RIGHTS) )
{
sent_fd = *((int *) CMSG_DATA(control_message));
return sent_fd;
}
}

return -1;
}

int main(int argc, char **argv)
{
const char *filename = "/tmp/z7.c";

if (argc > 1)
filename = argv[1];
int sv[2];
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) != 0)
fprintf(stderr,"Failed to create Unix-domain socket pair\n");

int pid = fork();
if (pid > 0) // in parent
{
fprintf(stderr,"Parent at work\n");
close(sv[1]);
int sock = sv[0];

int fd = open(filename, O_RDONLY);
if (fd < 0)
fprintf(stderr,"Failed to open file %s for reading %s\n", filename, strerror(errno));

fprintf(stderr,"FILE TO SEND HAS DESCRIPTOR: %d\n",fd);

/* Read some data to demonstrate that file offset is passed */
char buffer[32];
int nbytes = read(fd, buffer, sizeof(buffer));
if (nbytes > 0)
fprintf(stderr,"Parent read: [[%.*s]]\n", nbytes, buffer);

send_fd(sock, fd);

close(fd);

sleep(4);

fprintf(stderr,"Parent exits\n");
}
else // in child
{
fprintf(stderr,"Child at play\n");
close(sv[0]);
int sock = sv[1];

sleep(2);

int fd = recv_fd(sock);
printf("Read %d!\n", fd);
char buffer[256];
ssize_t nbytes;
while ((nbytes = read(fd, buffer, sizeof(buffer))) > 0) {
fprintf(stderr,"WRITING: %d\n",nbytes);
write(1, buffer, nbytes);
}
printf("Done: %d %s!\n",nbytes,strerror(errno));
close(fd);
}
return 0;
}

Answer

The file offset is shared by both processes. So when the parent process reads until EOF, there's nothing left for the child process to read.

This is the same as when two processes inherit a file descriptor from a parent, e.g. the shell command:

{ echo first cat; cat ; echo second cat ; cat ; } < filename

The first cat command will read all of the file, and the second cat will have nothing to read.