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.