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

CLinux

C Problem Overview


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?

C Solutions


Solution 1 - C

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 - C

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 - C

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.

Solution 4 - C

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 - C

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 - C

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionlive2dream95View Question on Stackoverflow
Solution 1 - CnosView Answer on Stackoverflow
Solution 2 - CkayView Answer on Stackoverflow
Solution 3 - CkolyptoView Answer on Stackoverflow
Solution 4 - CMarkRView Answer on Stackoverflow
Solution 5 - CStef BonView Answer on Stackoverflow
Solution 6 - CfrankscitechView Answer on Stackoverflow