Skip to content

add utils for passing file descriptor between processes - #4019

Open
echoechoin wants to merge 26 commits into
util-linux:masterfrom
echoechoin:feat/3683/fdsend
Open

add utils for passing file descriptor between processes#4019
echoechoin wants to merge 26 commits into
util-linux:masterfrom
echoechoin:feat/3683/fdsend

Conversation

@echoechoin

Copy link
Copy Markdown
Contributor

Based on the outcomes of Discussion #3683, I have developed this demo, with the relevant operation example as follows:

 ~/util-linux # ./build/fdrecv -f 0 test --run cat &
[1] 1200890
 ~/util-linux # echo "hello world" | ./build/fdsend -f 0 test
hello world
[1]  + 1200890 done       ./build/fdrecv -f 0 test --run cat

Comment thread misc-utils/fdsend-common.c Fixed
Comment thread misc-utils/fdsend-common.c Fixed
Comment thread misc-utils/fdsend-common.c Fixed
Comment thread misc-utils/fdsend-common.c Fixed
@echoechoin echoechoin changed the title [RFC] add utils for passing file descriptor between processes add utils for passing file descriptor between processes Feb 4, 2026
Comment thread .gitignore Outdated
Comment thread misc-utils/fdsend-common.c Outdated
}

/* get fd_to_send by open /proc/PID/fd/FD */
fd_to_send = open(proc_path, O_RDWR);

@masatake masatake Feb 4, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open(2) works for an fd opening a regular file.
For fds opening peculiar objects like sockets, we can use pidfd_getfd as a fallback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using pidfd_getfd at first, then fall back to open if errno == ENOSYS || errno == EPERM.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The semantics of calling open(/proc/$pid/fd/$fd) and of calling pidfd_getfd($fd) differ.
open makes a new open file object in the kernel.
pidfd_getfd doesn't. pidfd_getfd makes a new reference to the open file.

If a receiver process calls lseek on a received file descriptor, it affects on the open file behind the fd.

open(2)

 [target process]<fd> ------ [file object] --- [dentry] --- [inode]
                                              /
 [receiver process]<fd> -----[file object] --/

pidfd_getfd(2)

 [target process]<fd> ------ [file object] --- [dentry] --- [inode]
                             /
 [receiver process]<fd> ----/

I would like to withdraw my comment.

I think users expect the behavior of open(2). So, using the open(2) method is better as the default method.

What about activating the pidfd_getfd(2) method only if --pidfd-getfd option is given?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I believe it is better to activate the pidfd_getfd(2) method only if --pidfd-getfd option is given. No fallback.

@masatake

masatake commented Feb 4, 2026

Copy link
Copy Markdown
Member

It seems you need to add bash-completion files for your new commands.

Comment thread misc-utils/fdsend.c Outdated
Comment on lines +74 to +76
opt_pid = strtopid_or_err(optarg, _("invalid pid"));
if (opt_pid < 1)
errx(EXIT_FAILURE, _("pid must be positive"));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also use this internal lib function. It would make way for a potential PID:inode support which could be interesting for fd{recv,send}, since it'll help avoid sending/receiving a fd from/to a wrong process (in cases where that is important of course).

Suggested change
opt_pid = strtopid_or_err(optarg, _("invalid pid"));
if (opt_pid < 1)
errx(EXIT_FAILURE, _("pid must be positive"));
opt_pid = ul_parse_pid_str_or_err(optarg, &opt_pid, NULL);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some context in regards to PID:inode: #3953 (comment)

@masatake

masatake commented Feb 4, 2026

Copy link
Copy Markdown
Member

What about extending fdrecv to receive multiple fds?
We can add the implementation later, but we must design the CLI now in case.

@echoechoin

Copy link
Copy Markdown
Contributor Author

What about extending fdrecv to receive multiple fds? We can add the implementation later, but we must design the CLI now in case.

How about this:

# Send multiple fds:
fdsend --block --fd 0,1,2 -b pipe_test

# Receive multiple fds:
fdrecv --fd 0,1,2 pipe_test --run cat  # 0 <-> 0, 1 <-> 1, 2 <-> 2 (full mapping)
fdrecv --fd 0,1   pipe_test --run cat  # 0 <-> 0, 1 <-> 1 (2 is ignored)
fdrecv --fd 0     pipe_test --run cat  # 0 <-> 0 (1 and 2 are ignored)

Also ensure that --fd, --stdin, --stdout, and --stderr are mutually exclusive,
and --stdin, --stdout, and --stderr accepting only the first single fd.

# Send multiple fds:
fdsend --block --fd 0,1,2 -b pipe_test

# Receive only first fd via --stdin, --stdout, or --stderr:
fdrecv --stdin pipe_test --run cat # 0 <-> stdin, ignore other fds.

@echoechoin
echoechoin marked this pull request as ready for review February 6, 2026 03:08
@echoechoin
echoechoin force-pushed the feat/3683/fdsend branch 3 times, most recently from 4f0da87 to 215f250 Compare February 6, 2026 06:38
@masatake

masatake commented Feb 7, 2026

Copy link
Copy Markdown
Member

What about supporting an abstract Unix socket (https://man7.org/linux/man-pages/man7/unix.7.html)?
It may help users pass a file descriptor between processes in different mount namespaces.

@echoechoin

Copy link
Copy Markdown
Contributor Author

What about supporting an abstract Unix socket (https://man7.org/linux/man-pages/man7/unix.7.html)? It may help users pass a file descriptor between processes in different mount namespaces.

How about add a new command option for abstract unix socket?

fdsend --abstract --fd 1 sockspec
fdrecv --abstract --fd 1 sockspec run cat

or add an @ sign before sockspec to indicate it is a abstract unix socket:

fdsend --fd 1 @sockspec
fdrecv --fd 1 @sockspec run cat

@masatake

masatake commented Feb 9, 2026

Copy link
Copy Markdown
Member

Adding --abstract looks better for me.

Rationals:

--abstract option requires more type than @socket. It implies that passing an fd via an abstract socket requires care.

For abstract sockets, we cannot use inotify to detect the appearance of a listening socket. So the usability of these commands with --abstract differs from the commands without the option.

--abstract option allows users passing an fd via /run/user/$UID/fdsend/@fname :-)

@echoechoin

Copy link
Copy Markdown
Contributor Author

In abstract and blocking mode, may be we can retry on ECONNREFUSED at intervals (like 500ms) until connect succeed, then send the file descriptor. Or other approaches?

@masatake

masatake commented Feb 9, 2026

Copy link
Copy Markdown
Member

I don't have any other idea than the polling approach.
For implementing private tools, watching the unix_bind() invocation with BPF can be a choice. However, I think it is not suitable for a tool in util-linux.

* Receiver: socket/bind/listen/accept, recvmsg with SCM_RIGHTS.
* On success sets *out_fd to the received fd and returns 0.
*/
static int fdrecv_accept_and_recv_fd(const char *sockpath, int *out_fd, int abstract)

Check warning

Code scanning / CodeQL

Poorly documented large function Warning

Poorly documented function: fewer than 2% comments for a function of 114 lines.
Comment thread bash-completion/fdrecv
Comment thread misc-utils/fdsend.c Outdated
@echoechoin
echoechoin force-pushed the feat/3683/fdsend branch 2 times, most recently from f865d19 to 993f975 Compare February 10, 2026 06:14
@echoechoin
echoechoin requested a review from cgoesche February 10, 2026 06:29
Comment thread misc-utils/fdrecv.c Outdated
FILE *out = stdout;

fputs(USAGE_HEADER, out);
fprintf(out, _(" %s [options] SOCKSPEC --run command args...\n"), program_invocation_short_name);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find that --run is a bit unnecessary. Since SOCKSPEC can not be specified an arbitrary amount of time, we can simply design the CLI like this:

fdrecv [options] SOCKSPEC COMMAND

COMMAND will have an arbitrary amount of arguments, which is fine as these will be the remaining elements of argv and we'll just pass them to execvp(2).

What do you think ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#4019 (comment)

If we extend fdrecv to accept fds from multiple sockets, the fdcecv command line can become complicated. In these cases, the --run option helps people understand the command line.

If the action that fdrecv takes after receiving all fds is only "run", we can choose -- (or -) instead of --run.

Comment thread misc-utils/fdrecv.c Outdated
Comment thread misc-utils/fdrecv.c Outdated
Comment thread misc-utils/fdrecv.c Outdated
Comment thread misc-utils/fdrecv.c Outdated
Comment thread misc-utils/fdrecv.c Outdated
Comment thread misc-utils/fdsend.c Outdated
Comment thread misc-utils/fdsend.c Outdated
Comment thread misc-utils/fdsend.c Outdated
Comment thread misc-utils/fdsend-common.c Outdated
Comment thread misc-utils/fdsend-common.c Outdated
@masatake

Copy link
Copy Markdown
Member

It would be nice if fdrecv could receive multiple fds from different sockets at once.
This is just an idea.

terminal A

fdsend --fd 0 sockA

terminal B

fdsend --pid 9999 --fd 1 sockB

terminal C

fdsend --pid 9999 --fd 32 sockC

trminal recv:

fdrecv -f 3 sockA -f 2 sockB -f 0 sockC --run CMD

@echoechoin
echoechoin marked this pull request as draft August 4, 2026 05:58
This commit implements the fdsend and fdrecv utilities as proposed in
Discussion util-linux#3683. It adds:
- fdsend: send a file descriptor to a Unix domain socket (path-based).
  Supports blocking wait for receiver, optional pid/fd resolution, and
  pidfd_getfd(2) where available.
- fdrecv: receive a file descriptor from a Unix domain socket and
  optionally exec a program with the received fd.

Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Allow using pidfd_getfd(2) instead of open(/proc/PID/fd/FD) to obtain
the file descriptor from the process.

Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Add the shared fdsend-common.h to fdsend_SOURCES and fdrecv_SOURCES so
make dist packs the header.

Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
…usage message for clarity

Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
- Support multiple -f N SOCKSPEC (and -i/-o/-e SOCKSPEC) before --run CMD.
- Add `relocate_conflicting_fds` to avoid target FD and received FD conflicts.
- Add fdrecv doc update and tests.

Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Replace manual string copies and path splitting in sockpath_from_spec() and
fdsend_wait_for_socket() with xstrncpy(), xasprintf(), stripoff_last_component()
and ul_path_get_abspath().

Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
The option name --pidfd-getfd leaks an implementation detail without
describing the semantic difference from the default open(/proc/PID/fd/FD)
approach.  Rename to --dup (-d) to convey that the fd is duplicated
(shared offset and status flags) rather than opened as an independent
copy.

Also expand the man page to explain the difference between --dup and
the default behavior.

Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Accept --pid 12345:67890 syntax to validate the pidfd inode,
preventing PID reuse races when using --dup.

Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
Default remains O_RDWR.  Accepts --mode r (O_RDONLY), r+ (O_RDWR) or
w (O_WRONLY).  Mutually exclusive with --dup.

Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
SIGINT/SIGTERM/SIGHUP now break out of the blocking connect-retry loop
and the inotify socket-wait loop gracefully with EINTR instead of
requiring the process to be killed.
Merge the separate fdrecv and fdsend signal handler sets into one
shared handler.

Signed-off-by: WanBingjiang <wanbingjiang@webray.com.cn>
@echoechoin
echoechoin marked this pull request as ready for review August 4, 2026 10:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

TODO We going to think about it ;-)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants