PDFORK(2)

πŸ“¦ version 15.1, in freebsd 15.1

πŸ¦‹ PDFORK(2) β€” the process descriptor situation, explained by your resident Unix girlie

okay so this one is from the System Calls Manual and honestly? it's giving "your child process but make it a file descriptor" πŸ’… stay with me bestie, it's actually so good.

πŸ’– LIBRARY πŸ’–

Standard C Library (libc, -lc) β€” the usual ride-or-die.

✨ SYNOPSIS ✨

#include <sys/procdesc.h>

pid_t
pdfork(int *fdp, int pdflags);

pid_t
pdrfork(int *fdp, int pdflags, int rfflags);

int
pdgetpid(int fd, pid_t *pidp);

int
pdkill(int fd, int signum);

int
pdwait(int fd, int *status, int options, struct __wrusage *wrusage,
    struct __siginfo *info);

πŸŽ€ DESCRIPTION πŸŽ€

okay so process descriptors are these iconic special file descriptors that literally represent processes. you make one with pdfork() β€” it's a variant of fork(2) β€” and if it works, it drops a process descriptor into the integer that fdp points at. πŸ”₯

the big slay here: processes created via pdfork() will not cause SIGCHLD on termination. no cap.

pdfork() accepts these pdflags:

  • PD_DAEMON β€” instead of the default "terminate on close" behaviour, this lets the process live until it gets explicitly killed with kill(2). main character energy, honestly.
heads up bestie: this option is not permitted in capsicum(4) capability mode (go look at cap_enter(2)).
  • PD_CLOEXEC β€” sets close-on-exec on the process descriptor.

pdrfork() is the variant of pdfork() that also takes the rfflags argument, which controls how process resources get shared between the caller and the new process. like pdfork(), it writes the process descriptor referencing the created process into the location fdp points to. for the full rundown of possible rfflag flags, see rfork(2). ⭐

⚠️ pdrfork() requires that you specify both the RFPROC and RFPROCDESC flags, or the RFSPAWN flag. no vibes-based guessing allowed.

pdgetpid() queries the process ID (PID) inside the process descriptor fd. cute little lookup, love that for us. 🫢

pdkill() is functionally identical to kill(2), except it takes a process descriptor fd instead of a PID. same energy, different paperwork.

pdwait() lets the calling thread wait and retrieve the status info on the process referenced by the fd process descriptor. for the behaviour spec, see the description of the wait6 system call. πŸ‘‘


the following system calls also have effects that are specific to process descriptors:

  • fstat(2) queries the status of a process descriptor. currently only the st_mode, st_birthtime, st_atime, st_ctime and st_mtime fields are defined. and here's the cute trick: if the owner read, write, and execute bits are set, then the process represented by the process descriptor is still alive. πŸ«€
  • poll(2) and select(2) let you wait for process state transitions. currently only POLLHUP is defined, and it gets raised when the process dies. process state transitions can also be monitored with the kqueue(2) filter EVFILT_PROCDESC; currently only NOTE_EXIT is implemented.
  • close(2) closes the process descriptor unless PD_DAEMON is set. if the process is still alive and this is the last reference to the process descriptor, the process gets terminated with the signal SIGKILL. πŸ’€ and ngl this part matters: the PID of the referenced process is not reused until the process descriptor is closed β€” whether or not the zombie process is reaped by pdwait(), wait6, or similar system calls.

🌸 RETURN VALUES 🌸

pdfork() and pdrfork() return a PID, 0 or -1, exactly as fork(2) does. classic.

pdgetpid(), pdkill(), and pdwait() return 0 on success and -1 on failure.

πŸ”₯ ERRORS πŸ”₯

these functions may return the same error numbers as their PID-based equivalents (e.g. pdfork() may return the same error numbers as fork(2)), plus these additions:

  • [EFAULT] β€” the copyout of the resulting file descriptor value to the memory pointed to by fdp failed.
😭 okay this one is a whole thing. note that the child process was already created when this condition is detected, and the child continues execution, same as the parent. if you actually need to handle this error, it's advisable to memoize the getpid() result before the call to pdfork() or pdrfork(), and compare it to the value returned by getpid() after, so you can see whether code is executing in the parent or the child. fr fr, do not skip this.
  • [EINVAL] β€” the signal number given to pdkill() is invalid.
  • [ENOTCAPABLE] β€” the process descriptor being operated on has insufficient rights (e.g. CAP_PDKILL for pdkill()).

πŸ’œ SEE ALSO πŸ’œ

close(2), fork(2), fstat(2), kill(2), kqueue(2), poll(2), wait4(2), capsicum(4), procdesc(4)

🌈 HISTORY 🌈

pdfork(), pdgetpid(), and pdkill() first appeared in FreeBSD 9.0. pdrfork() and pdwait() first appeared in FreeBSD 15.1.

support for process descriptors mode was developed as part of the TrustedBSD Project. 🧚

πŸ‘‘ AUTHORS πŸ‘‘

these functions and the capability facility were created by Robert N. M. Watson <rwatson@FreeBSD.org> and Jonathan Anderson <jonathan@FreeBSD.org> at the University of Cambridge Computer Laboratory, with support from a grant from Google, Inc. the pdrfork() and pdwait() functions were developed by Konstantin Belousov <kib@FreeBSD.org> with input from Alan Somers <asomers@FreeBSD.org>. absolute legends, all of them. 🫢


and that's the whole page bestie, ily, go fork responsibly πŸ’…βœ¨

this slice of Debian-flavoured wisdom is dated April 19, 2026, and yes, the page itself is PDFORK(2) β€” now go read procdesc(4) and text me later πŸŽ€

πŸ“¦ where this page comes from
  • freebsd 15.1: base 15.1
  • πŸ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms