Say I have 2 processes, ProcessA and ProcessB. If I perform int fd=open(somefile) in ProcessA, can I then pass the value of file descriptor fd over IPC to ProcessB and have it manipulate the same file?

Solution 1

You can pass a file descriptor to another process over unix domain sockets. Here's the code to pass such a file descriptor, taken from Unix Network Programming

ssize_t
write_fd(int fd, void *ptr, size_t nbytes, int sendfd)
{
    struct msghdr   msg;
    struct iovec    iov[1];

#ifdef  HAVE_MSGHDR_MSG_CONTROL
    union {
      struct cmsghdr    cm;
      char              control[CMSG_SPACE(sizeof(int))];
    } control_un;
    struct cmsghdr  *cmptr;

    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);

    cmptr = CMSG_FIRSTHDR(&msg);
    cmptr->cmsg_len = CMSG_LEN(sizeof(int));
    cmptr->cmsg_level = SOL_SOCKET;
    cmptr->cmsg_type = SCM_RIGHTS;
    *((int *) CMSG_DATA(cmptr)) = sendfd;
#else
    msg.msg_accrights = (caddr_t) &sendfd;
    msg.msg_accrightslen = sizeof(int);
#endif

    msg.msg_name = NULL;
    msg.msg_namelen = 0;

    iov[0].iov_base = ptr;
    iov[0].iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    return(sendmsg(fd, &msg, 0));
}
/* end write_fd */

And here's the code to receive the file descriptor

ssize_t
read_fd(int fd, void *ptr, size_t nbytes, int *recvfd)
{
    struct msghdr   msg;
    struct iovec    iov[1];
    ssize_t         n;
    int             newfd;

#ifdef  HAVE_MSGHDR_MSG_CONTROL
    union {
      struct cmsghdr    cm;
      char              control[CMSG_SPACE(sizeof(int))];
    } control_un;
    struct cmsghdr  *cmptr;

    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);
#else
    msg.msg_accrights = (caddr_t) &newfd;
    msg.msg_accrightslen = sizeof(int);
#endif

    msg.msg_name = NULL;
    msg.msg_namelen = 0;

    iov[0].iov_base = ptr;
    iov[0].iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    if ( (n = recvmsg(fd, &msg, 0)) <= 0)
        return(n);

#ifdef  HAVE_MSGHDR_MSG_CONTROL
    if ( (cmptr = CMSG_FIRSTHDR(&msg)) != NULL &&
        cmptr->cmsg_len == CMSG_LEN(sizeof(int))) {
        if (cmptr->cmsg_level != SOL_SOCKET)
            err_quit("control level != SOL_SOCKET");
        if (cmptr->cmsg_type != SCM_RIGHTS)
            err_quit("control type != SCM_RIGHTS");
        *recvfd = *((int *) CMSG_DATA(cmptr));
    } else
        *recvfd = -1;       /* descriptor was not passed */
#else
/* *INDENT-OFF* */
    if (msg.msg_accrightslen == sizeof(int))
        *recvfd = newfd;
    else
        *recvfd = -1;       /* descriptor was not passed */
/* *INDENT-ON* */
#endif

    return(n);
}
/* end read_fd */

Solution 2

If both processes belong the the same user, then you can simply make use of the procfs.

char fd_path[64];  // actual maximal length: 37 for 64bit systems
snprintf(fd_path, sizeof(fd_path), "/proc/%d/fd/%d", SOURCE_PID, SOURCE_FD);
int new_fd = open(fd_path, O_RDWR);

Of course you would need to some IPC mechanism to share the value of SOURCE_FD. See e.g. Linux C: upon receiving a signal, is it possible to know the PID of the sender?.

Solution 3

In 2020, on Linux versions 5.6 and above, a new system call was added to Linux thatll enable a process to obtain a duplicate of a file descriptor of another process referred to by a pidfd with the pidfd_getfd() system call.

Solution 4

You can use the method nos described in this thread, or the (more conventional) way, by sharing it between related processes (typically parent-child or siblings) by having it created, the forked processes automatically receive a copy.

Indeed, forked processes get all your FDs and can use them unless they close them (which is generally a good idea).

Therefore if a parent forks two children, if they both have a file descriptor they didn't close, it is now shared (even if the parent subsequently closes it). This could, for example, be a pipe from one child to another. This is how shell redirects like

ls -l | more

Work.

Solution 5

Note that in the example of above, the setting of variables when receiving, like:

msg.msg_name = NULL;
msg.msg_namelen = 0;

iov[0].iov_base = ptr;
iov[0].iov_len = nbytes;
msg.msg_iov = iov;
msg.msg_iovlen = 1;

is not required. The whole idea of a message structure with headers is that the receiving site does not have to know what it reads, and can by checking the (first) header, what kind of message it is and what to expect.

Solution 6

Short Answer:

Try pidfd_getfd

Long Answer

The pidfd_getfd() system call allocates a new file descriptor in the calling process (Process B). This new file descriptor is a duplicate of an existing file descriptor, targetfd, in the process (Process A) referred to by the PID file descriptor pidfd . Of course you need a mechanism to get targetfd from Process A.

newfd = syscall(SYS_pidfd_getfd, int pidfd, int targetfd, 0);

We get the PID file descriptor pidfd from pidfd_open().

 pidfd = syscall(SYS_pidfd_open, pid_t pid, 0);

The effect of pidfd_getfd() is similar to the use of SCM_RIGHTS messeges, but in order to pass a file descriptor using an SCM_RIGHTS message, the two processes must first establish a UNIX domain socket connection, so requires cooperation on the part of the process whose file descriptor is being copied. By contrast, no such cooperation is necessary when using pidfd_getfd().

A dummy example

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <string.h>
#define MMAP_NAME "/tmp/mmap"
struct shared_mem{
    int targetfd;
};
int main(void){
    int fd;
    struct shared_mem *shmp;
    unlink(MMAP_NAME);
    fd = open(MMAP_NAME, O_CREAT | O_RDWR, 00600);
    ftruncate(fd, sizeof(struct shared_mem));
    shmp = (struct shared_mem *)mmap(NULL,
                                     sizeof(struct shared_mem),
                                     PROT_READ | PROT_WRITE,
                                     MAP_SHARED,
                                     fd,
                                     0);
    if (fork() == 0){
        sleep(5);
        write(syscall(SYS_pidfd_getfd,
                      syscall(SYS_pidfd_open, getppid(), 0),
                      shmp->targetfd,
                      0),
              "Messege from Child\n",
              strlen("Messege from Child\n"));
        close(shmp->targetfd);
        exit(EXIT_SUCCESS);
    }else{
        shmp->targetfd = open("foo.txt", O_RDWR | O_CREAT);
        write(shmp->targetfd, "Messege from Parent\n", strlen("Messege from Parent\n"));
        wait(NULL);
    }
    munmap(shmp, sizeof(struct shared_mem));
    return EXIT_SUCCESS;
}

..

# cat foo.txt
Messege from Parent
Messege from Child