Skip to content
FreeBSDDeep Dive Published Updated 7 min readViews unavailable

FreeBSD fdescfs Operations: Expose Open Descriptors Through /dev/fd

Understand FreeBSD fdescfs pathname semantics, mount options, descriptor duplication, jail integration, and safe validation of /dev/fd consumers.

FreeBSD’s fdescfs(4) exposes a process’s open file-descriptor namespace through pathnames such as /dev/fd/0. It is a pseudo-filesystem view, not a disk directory containing copies of open files. Opening one of these paths has descriptor-specific semantics that matter to shell pipelines, compatibility environments, service wrappers, and applications that expect descriptors above standard input, output, and error.

By default, devfs provides /dev/fd/0, /dev/fd/1, and /dev/fd/2. fdescfs can expose all open descriptors for the process accessing the directory. This is why a program may successfully use the standard descriptors without an fdescfs mount yet fail when it expects /dev/fd/3 or a higher descriptor. Confirm the required range and mount context before changing /dev.

Verify the existing /dev/fd implementation

Record the release, current mounts, and existing descriptor paths:

freebsd-version -kru
mount -p | grep -E 'devfs|fdescfs'
ls -l /dev/fd
ls -l /dev/stdin /dev/stdout /dev/stderr
procstat -f "$$"

The fd(4) and devfs(4) manuals describe standard descriptor nodes available through devfs; fdescfs(4) provides entries for descriptors open in the process performing the directory operation. A plain ls -l /dev/fd is an external process on a typical shell, so it reports the descriptors visible to ls, not necessarily every descriptor held by its interactive parent shell. To inspect a known process explicitly, use procstat -f PID where permissions allow; from a POSIX shell, procstat -f "$$" targets that shell. The descriptor set can still change immediately after either observation.

Check whether the application uses /dev/fd/N, /dev/stdin, or a runtime-specific path. Search its documented command line and logs before adding a global mount. If a failure happens only in a jail or Linux compatibility root, inspect that environment’s mount table and compatibility settings instead of assuming the host’s /dev/fd applies inside every namespace.

Mount fdescfs deliberately

The documented command mounts the filesystem at /dev/fd:

mount -t fdescfs none /dev/fd
mount -p | grep fdescfs
ls -l /dev/fd

Use the exact source token and filesystem type from the installed fdescfs(4) manual. Mounting over an existing path changes what that path resolves to while active. Verify the mount table and confirm expected descriptors are accessible before restarting the dependent service.

For a persistent compatibility mount, the FreeBSD Handbook documents fdescfs entries in /etc/fstab, often with late when used under a compatibility root. A schematic entry is:

fdescfs  /compat/linux/dev/fd  fdescfs  rw,late,linrdlnk  0  0

Do not copy this path or options blindly. linrdlnk changes vnode type reporting for Linux ABI compatibility; it is not a general-purpose performance switch. A host’s normal /dev/fd does not usually need that option. Keep the mount point and options aligned with the specific consumer.

The filesystem can be mounted at a dedicated alternate path for a controlled test:

install -d -m 0755 /var/run/fd-view
mount -t fdescfs none /var/run/fd-view
ls -l /var/run/fd-view
umount /var/run/fd-view

This verifies that the filesystem can be mounted without replacing conventional /dev/fd. It does not prove that an application hard-coded to /dev/fd will work until the intended path is available in that application’s environment.

Understand descriptor opening semantics

Without the nodup mount option, if a referenced descriptor is open and the requested access mode is a subset of the existing descriptor’s mode, opening /dev/fd/N is equivalent to duplicating that descriptor with fcntl(F_DUPFD). This is not the same as opening the original pathname again. It refers to the already-open object and shares the underlying open-file state, including offsets for ordinary seekable files.

Opening /dev/stdin, /dev/stdout, or /dev/stderr is similarly equivalent to duplicating descriptors 0, 1, and 2, subject to the existing descriptor and requested access mode. Flags other than O_RDONLY, O_WRONLY, and O_RDWR are ignored by the documented fd(4) interface. Do not assume open(“/dev/fd/3”, O_CREAT, …) creates a new file or changes the original descriptor’s status flags.

The nodup option changes behavior for descriptors referencing vnodes: the path reopens the referenced vnode with the requested mode rather than duplicating the descriptor. The manual describes this as equivalent to openat(fd, "", O_EMPTY_PATH, mode) and notes that current permissions must allow the requested mode. This is a semantic change, not a harmless tuning option. Select it only when a specific application requires the documented behavior and has been tested with that mount configuration.

The rdlnk option treats fdescfs vnodes consistently as symbolic links, including path lookup behavior. linrdlnk reports the vnode type as VLNK for Linux ABI compatibility but is weaker than rdlnk. These options affect how applications interpret the filesystem. Avoid stacking them without a compatibility requirement, and test scripts that use test -L, stat, or path traversal.

Diagnose missing descriptor paths

If /dev/fd/0 through /dev/fd/2 exist but /dev/fd/3 does not, determine whether the program actually has descriptor 3 open. Use application diagnostics or procstat -f PID where permitted. fdescfs exposes descriptors that exist in the process accessing the directory; it does not create arbitrary numbers.

If an application receives ENOENT, check the exact path and mount namespace. If it gets EBADF, the descriptor may not be open or may have closed before the access. If it gets EACCES, compare the requested mode with the existing descriptor and process credentials. A mismatch can be caused by descriptor lifecycle or access mode rather than a broken mount.

A shell can open a descriptor and pass it to a child process, but close-on-exec behavior and shell implementation determine whether it survives exec. Inspect the producer’s FD_CLOEXEC behavior and the process’s actual descriptor table. The existence of /dev/fd alone does not guarantee descriptor inheritance.

For automation that builds pipelines, test a minimal producer and consumer before embedding the path in a production wrapper. Confirm whether the consumer duplicates the descriptor or reopens the file; those operations can differ in offset and permission behavior. Avoid replacing a supported pipe or standard input interface with /dev/fd/N merely because the path is available.

Use in jails and compatibility environments

The host mount table and a jail’s view are not automatically interchangeable. If a jail requires /dev/fd, inspect jail configuration, mount sequence, devfs rules, and filesystem visibility at its root. Expose only the needed path and ensure it represents the intended process context. A pathname visible inside one jail must not be assumed to refer to descriptors belonging to unrelated host processes; fdescfs is process-relative.

The Handbook’s Linux compatibility instructions include fdescfs under the compatibility tree. linrdlnk exists specifically for that ABI behavior. Keep compatibility mounts separate from the host’s conventional /dev/fd mount and test after the Linuxulator environment starts. Do not apply linrdlnk to the regular host mount as a universal fix for an application that misinterprets vnode types.

When a jail or compatibility environment starts before a required mount is ready, the application may observe an empty ordinary directory instead. Use documented late mount ordering where appropriate, verify the final mount table after boot, and check the service startup dependency. A successful fstab parse does not prove the mount succeeded at boot.

Operational boundaries

With the default non-nodup behavior, descriptor paths refer to objects already opened by the calling process; they do not reopen an unrelated pathname. With nodup, vnode descriptors are reopened and the requested mode is checked against current permissions. Do not expose or rely on a process’s descriptor namespace across a trust boundary without understanding which process context and mount options apply. Avoid world-writable mount points and do not share an fdescfs path as ad hoc IPC.

Before changing a mount on a running system, identify consumers that have the directory open or use it as a working directory. Unmounting can fail while the filesystem is busy; forcing it can create confusing path transitions. Use fstat and process inspection to locate references, stop only the relevant consumer through its supported service interface, unmount, and verify the mount table afterward.

Acceptance criteria

An fdescfs configuration is ready when the required descriptor path exists in the correct process namespace, the consumer’s open/duplicate/reopen expectations are understood, mount options are justified, and boot or jail ordering is verified. A failure is resolved when the process has the expected descriptor open at lookup time and the requested access mode matches its intended semantics.

Treat /dev/fd as a view of open process state. Validate the mount and descriptor lifecycle together; changing the filesystem type alone cannot repair a descriptor that was never inherited, was closed early, or is opened with an incompatible mode.

Related:

Sources:

Comments