Resolving pathnames

Updated: May 06, 2022

When a process opens a file, the POSIX-compliant open() library routine first sends the pathname to procnto, where the pathname is compared against the prefix tree to determine which resource managers should be sent the open() message.

The prefix tree may contain identical or partially overlapping regions of authority—multiple servers can register the same prefix. If the regions are identical, the order of resolution can be specified (see “Ordering mountpoints,” below). If the regions are overlapping, the responses from the path manager are ordered with the longest prefixes first; for prefixes of equal length, the same specified order of resolution applies as for identical regions.

For example, suppose we have these prefixes registered:

Prefix Description
/ Power-Safe filesystem (fs-qnx6.so)
/dev/ser1 Serial device manager (devc-ser*)
/dev/ser2 Serial device manager (devc-ser*)
/dev/hd0 Raw disk volume (devb-eide)

The filesystem manager has registered a prefix for a mounted Power-Safe filesystem (i.e., /). The block device driver has registered a prefix for a block special file that represents an entire physical hard drive (i.e., /dev/hd0). The serial device manager has registered two prefixes for the two PC serial ports.

The following table illustrates the longest-match rule for pathname resolution:

This pathname: matches: and resolves to:
/dev/ser1 /dev/ser1 devc-ser*
/dev/ser2 /dev/ser2 devc-ser*
/dev/ser / fs-qnx6.so
/dev/hd0 /dev/hd0 devb-eide.so
/usr/jhsmith/test / fs-qnx6.so

Ordering mountpoints

Generally the order of resolving a filename is the order in which you mounted the filesystems at the same mountpoint (i.e., new mounts go on top of or in front of any existing ones). You can specify the order of resolution when you mount the filesystem. For example, you can use:

You can also use the -o option to mount with these keywords:

before
Mount the filesystem so that it's resolved before any other filesystems mounted at the same pathname (in other words, it's placed in front of any existing mount). When you access a file, the system looks on this filesystem first.
after
Mount the filesystem so that it's resolved after any other filesystems mounted at the same pathname (in other words, it's placed behind any existing mounts). When you access a file, the system looks on this filesystem last, and only if the file wasn't found on any other filesystems.

If you specify the appropriate before option, the filesystem floats in front of any other filesystems mounted at the same mountpoint, except those that you later mount with before. If you specify after, the filesystem goes behind any other filesystems mounted at the same mountpoint, except those that are already mounted with after. So, the search order for these filesystems is:

  1. those mounted with before
  2. those mounted with no flags
  3. those mounted with after

with each list searched in order of mount requests. The first server to claim the name gets it. You would typically use after to have a filesystem wait at the back and pick up things the no one else is handling, and before to make sure a filesystem looks first at filenames.

Single-device mountpoints

Consider an example involving three servers:

Server A
A Power-Safe filesystem. Its mountpoint is /. It contains the files bin/true and bin/false.
Server B
A flash filesystem. Its mountpoint is /bin. It contains the files ls and echo.
Server C
A single device that generates numbers. Its mountpoint is /dev/random.

At this point, the process manager's internal mount table would look like this:

Mountpoint Server
/ Server A (Power-Safe filesystem)
/bin Server B (flash filesystem)
/dev/random Server C (device)

Of course, each “Server” name is actually an abbreviation for the nd, pid, chid for that particular server channel.

Now suppose a client wants to send a message to Server C. The client's code might look like this:

int fd;
fd = open("/dev/random", ...);
read(fd, ...);
close(fd);

In this case, the C library will ask the process manager for the servers that could potentially handle the path /dev/random. The process manager would return a list of servers:

From this information, the library will then contact each server in turn and send it an open message, including the component of the path that the server should validate:

  1. Server C receives a null path, since the request came in on the same path as the mountpoint.
  2. Server A receives the path dev/random, since its mountpoint was /.

As soon as one server positively acknowledges the request, the library won't contact the remaining servers. This means Server A is contacted only if Server C denies the request.

This process is fairly straightforward with single device entries, where the first server is generally the server that will handle the request. Where it becomes interesting is in the case of unioned filesystem mountpoints.

Unioned filesystem mountpoints

Let's assume we have two servers set up as before:

Server A
A Power-Safe filesystem. Its mountpoint is /. It contains the files bin/true and bin/false.
Server B
A flash filesystem. Its mountpoint is /bin. It contains the files ls and echo.

Note that each server has a /bin directory, but with different contents.

Once both servers are mounted, you would see the following due to the union of the mountpoints:

/
Server A
/bin
Servers A and B
/bin/echo
Server B
/bin/false
Server A
/bin/ls
Server B
/bin/true
Server A

What's happening here is that the resolution for the path /bin takes place as before, but rather than limit the return to just one connection ID, all the servers are contacted and asked about their handling for the path:

DIR *dirp;
dirp = opendir("/bin", ...);
closedir(dirp);

which results in:

  1. Server B receives a null path, since the request came in on the same path as the mountpoint.
  2. Server A receives the path "bin", since its mountpoint was "/".

The result now is that we have a collection of file descriptors to servers who handle the path /bin (in this case two servers); the actual directory name entries are read in turn when a readdir() is called. If any of the names in the directory are accessed with a regular open, then the normal resolution procedure takes place and only one server is accessed.

For a more detailed look at this, see Unioned filesystems in the Resource Managers chapter of Getting Started with QNX Neutrino.

Note: Running more than one pass-through filesystem or resource manager on overlapping pathname spaces might cause deadlocks.

Why overlay mountpoints?

This overlaying of mountpoints is a very handy feature when doing field updates, servicing, etc. It also makes for a more unified system, where pathnames result in connections to servers regardless of what services they're providing, thus resulting in a more unified API.