In traditional Unix, file systems contain special files. These are: symbolic links, character devices, block devices, named pipes, and named sockets. Naturally the Hurd also support these.

However, if you take a look at hurd/io.defs and hurd/fs.defs, you'll find that there are no RPCs that deal specifically with these types. Sure, you can get the type of the file through io_stat (among other things), but there are none that e.g. lets you create a symbolic link.

If you take a look at how glibc implements symlink, you'll notice that all it does is create a new file and set its passive translator to /hurd/symlink DEST. You can verify this yourself by creating a symlink with ln -s foo bar and print its passive translator setting with showtrans bar.

This is how the other special files are implemented as well. The header hurd/paths.h contains a list of paths that are used to implement special files:

  • /hurd/symlink
  • /hurd/chrdev
  • /hurd/blkdev
  • /hurd/fifo
  • /hurd/ifsock

So all special files are implemented through special-purpose translators, right? Not quite, instead the translators of this list are often implemented in their underlying filesystem through translator short-circuiting. In fact, chrdev and blkdev aren't even implemented as translators at all.

Translator short-circuiting is when a file system server implements the functionality of a passive translator itself, instead of actually starting it. For instance, all the ?symlink translator does is return a FS RETRY * reply to the caller. So instead of starting it, the file system server can simply continue the file name look-up internally by appending the target of the symbolic link to the path being looked-up.

This way, we can skip starting the symlink translator, skip retrying the look-up on the newly started translator, and we might also skip a retry to the same file system server again, if the target of the symbolic link is in it.

In fact, the list's translators that actually are implemented (symlink, fifo, ifsock) are only used as a default implementation if the underlying file system's translator does not implement the functionality itself, i.e., if it doesn't short-circuit it.

To make sure that you use one of these translators, there by bypassing the short-circuiting mechanism, you can either start it as an active translator, or use a different path from the one in hurd/path.h, e.g. settrans bar /hurd/./symlink foo. There is also a FS_TRANS_FORCE flag defined for the file_set_translator RPCs, but it currently isn't set from anywhere.

The best example of how short-circuiting is implemented can be found in libdiskfs. Notice how it detects if a translator to store is a special file in diskfs_S_file_set_translator and instead of storing a real passive translator setting on the disk, stores it as a symlink node (using diskfs_create_symlink_hook or a generic implementation).

In later look-ups to the node, it checks the node's stat structure in diskfs_S_file_get_translator, or diskfs_S_dir_lookup and handles special file types appropriately.

Doing this translator short-circuiting has disadvantages: code duplication, or in general adding code complexity that isn't needed for implementing the same functionality, but it also has advantages: using functionality that the file system's data structures nevertheless already provide -- storing symbolic links in ext2fs' inodes instead of storing passive translator settings -- and thus staying compatible with other operating systems mounting that file system.

Also, this short-circuiting does preserve system resources, as it's no longer required to start a symlink translator for resolving each symbolic link, as well as it does reduce the RPC overhead.

It can also confuse users who expect the passive translator to start. For instance, if a user notices that ?symlink's code is lacking some functionality, but that it unexpectedly works when the user tries to run it.