2

I'm trying to send a file descriptor between a socketpair with the code pasted below. This code is from: http://www.techdeviancy.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;
}
4
  • 3
    It's normal for the FD number to change. You're passing the stream, descriptor numbers are specific to a process. Commented Jun 17, 2016 at 16:02
  • Thanks - if the descriptor is open though, howcome the read function on the "3" file descriptor has 0 bytes to be read? I would expect it to be able to read just like the sender. Commented Jun 17, 2016 at 16:03
  • 1
    Is there anything more to read? Both processes share the same stream position, so if the parent has read to EOF, there's nothing left for the child to read. Commented Jun 17, 2016 at 16:04
  • wow - that was it. Thanks alot for your help. Commented Jun 17, 2016 at 16:06

3 Answers 3

1

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.

Sign up to request clarification or add additional context in comments.

Comments

0

Quoting Richard Stevens (Programming UNIX networks):

"It is normal that the descriptor number in the receiving process differs from the descriptor number in the sending process. Passing a descriptor isn't passing the descriptor number, instead a new descriptor is created in the receiving process that points to the same file entry in the kernel as the descriptor that was sent by the transmitting process."

Comments

0

Barmar said was right.

And I complete some code to make thing right. That is seek to begin of the file:

lseek(fd, 0,SEEK_SET);

Code snippet

    int fd = recv_fd(sock);
    printf("Read %d!\n", fd);
    lseek(fd, 0,SEEK_SET);

    char buffer[256];
    ssize_t nbytes;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.