Can I share a file descriptor to another process on linux or are they local to the process?
Asked Answered
T

6

70

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?

Taub answered 1/3, 2010 at 20:5 Comment(2)
See this question.Nephron
Duplicate: stackoverflow.com/questions/1997622Hussite
B
67

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 */
Bicipital answered 1/3, 2010 at 20:27 Comment(3)
Note though that the actual numerical value of the file descriptor will in general be different in the two processes.Seigniorage
You can pass the number this way. That doesn't magically mean it will work as a file descriptor to the same file at both ends.Bight
@EJP The idea with SCM_RIGHTS is that it will. Though none comes to mind, I'm sure there are some caveats. (i.e., the concept works pretty much like dup(), but between unrelated processes)Bicipital
F
10

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?”.

Fournier answered 14/2, 2017 at 3:41 Comment(4)
Would you mean "/proc/%d/fd/%d" within the snprintf?Deformed
For future readers: this works for real files but definitely does not work for things like un-named domain sockets (socketpair()), probably won't work for named domain sockets and I'm not sure about pipes. For domain sockets if you call open() on the file system path you'll get ENXIO; connect() would fail because it only makes sense in the context of an un-connected domain socket; bind() will say address already in use.Ney
open("/proc/9256/fd/5", O_WRONLY|O_CREAT|O_TRUNC, 0666) = -1 ENXIO (No such device or address) When trying to open a TCP connected socket the way you suggest. open(2) simply doesn't allow for opening a socket file from /proc//fdAbloom
This answer is incorrect. This does NOT share a file descriptor like how a file descriptor is shared when doing a fork(). This opens a new file descriptor to the same file. 1. There are many kinds of file descriptors for which this does not work at all. 2. When this does work, the state of the "file description" such as the seek position is not sharedPurposive
G
9

In 2020, on Linux versions 5.6 and above, a new system call was added to Linux that’ll 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.

Gwenngwenneth answered 23/8, 2021 at 23:14 Comment(0)
D
4

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
Drivein answered 6/5, 2022 at 3:25 Comment(2)
Let's assume we can't create the file descriptor before the fork().Drivein
Can u please tell. Can I get all opened file descriptor of a processLongford
M
2

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.

Mali answered 1/3, 2010 at 22:16 Comment(0)
E
2

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.

Ermeena answered 8/3, 2012 at 11:40 Comment(1)
While you’re technically correct, there’s a good reason to specify a buffer in this case: in order to send an OOB message (the socket control message in this case), you need to specify a non-empty message (see unix_stream_sendmsg, e.g. lxr.free-electrons.com/source/net/unix/af_unix.c#L1836). When receiving without an iovec, Linux will deliver that message over and over again. Hence, to read more than one OOB messages, you MUST read the message data at some point.Pruinose

© 2022 - 2024 — McMap. All rights reserved.