Architecture of the Starnix VFS

The page examines the key concepts and underlying structure of Starnix's virtual file system (VFS):

Motivation for Starnix's own virtual file system

Starnix has its own virtual file system (which is separate from Fuchsia's VFS) for the reasons below:

  • Performance – The Starnix VFS allows many file system operations to avoid making FIDL calls. The Starnix VFS does this by caching the results from previous FIDL calls and implementing the entire file system in a process (similar to tmpfs and procfs).

  • Compatibility – A file system for Starnix must support several Linux file system operations that are not supported by Fuchsia's VFS libraries, which many Linux-based operating systems rely on. In particular, the following two operations are hard to retrofit into Fuchsia's existing VFS design:

    • Mounting directories – A Linux file system allows a directory of one file system to be mounted to a directory of another file system.

    • Tracking a file's path after open() – A Linux file system can use a file descriptor to determine the path of the file that is opened by an open() call. If the file or one of its parent directories gets moved or renamed, the file's path must account for this change.

Building blocks of the Starnix VFS

This section constructs a simple file system using the fundamental building blocks of the Starnix VFS:

Files and directories

At the core of the Starnix VFS is a collection of FsNode instances. Each FsNode contains information about a file or a directory (see Figure 1). File system operations, such as reading, writing, and retrieving file information, are performed on FsNode instances.

A simple file system

Figure 1. A simple file system with a directory and a file.

A DirEntry assigns a name to an FsNode. This mapping of DirEntry to FsNode is referred to as a hard link. Multiple DirEntry instances can be mapped to a single file-typed FsNode (see Figure 2). This allows multiple paths in a file system to be resolved to the same FsNode. As a result, multiple paths can be resolved to the same file content and attributes in a file system. However, while a file can have multiple hard links, a directory (that is, a directory-typed FsNode) can only have one hard link.

A file system with two DirEntry instances

Figure 2. Multiple DirEntry instances can be mapped to the same FsNode.

A file system

A file system tracks information on a group of related files and directories. In the Starnix VFS, a file system (that is, a single instance of FileSystem) represents a collection of DirEntry instances, and each FileSystem instance contains the following items:

  • A DirEntry pointing to the root directory
  • A cache of FsNode instances (representing files and directories under the root directory)

A FileSystem

Figure 3. A FileSystem tracks DirEntry and FsNode instances.

Mounted file systems in the Starnix VFS

Mounting allows users to seamlessly move from one file system to another file system. A file system and its contents become available to users (or tasks) once the file system is mounted to a certain directory. This "mount point" directory is often a directory of another file system that the users already have access to. For example, in Figure 4, users can reach the file1.txt file in the Example FS (file system) by using the path /example/file1.txt from the Parent FS.

Mounted file systems

Figure 4. The root directory of the Example FS is mounted to the example directory of the Parent FS.

Mounting a file system and tracking mount points in the Starnix VFS involve the following instances:

Mount

A Mount makes a FileSystem accessible at the mount point directory of another FileSystem. To put it differently, a Mount is used to link the following two directories:

  • A mount point directory in the parent FileSystem
  • The root directory of the child FileSystem

In Figure 4, the Parent FS has the example directory as a subdirectory and the Example FS is mounted on this example directory (which is the mount point directory). In this setup, when you change your working directory to the example directory in the Parent FS (for instance, cd example is run), you "enter the mount," that is, you are now in the root directory of the Example FS.

A mounted file system

Figure 5. A Mount tracks the root directory of a mounted FileSystem.

To enable the feature of seamlessly moving from the parent FileSystem into the child FileSystem and vice versa, a Mount tracks a DirEntry that points to the root directory of the mounted FileSystem (see Figure 5). And a Mount also tracks its parent Mount using the mountpoint pointer (see NamespaceNode) and its child Mount instances using the submounts pointer. (see Figure 6).

Mount instances

Figure 6. Mount's mountpoint and submounts pointers are used to move between mounted file systems.

It's important to note that a FileSystem can be mounted to a number of different FileSystem instances at once. This allows the same FileSystem to be reached from multiple parent FileSystem instances, enabling various paths (or symlinks) to be used to reach the same file in the mounted FileSystem.

NamespaceNode

Every task in Starnix contains an instance of FsContext. An FsContext holds information about the task's association with the Starnix VFS, except for the file descriptor table.

Importantly, an FsContext contains a Namespace, which enables the task to identify all FileSystem instances that are mounted under this namespace. This is possible because each Namespace contains a Mount that tracks the "root mount" in the namespace (see Figure 7). Using the root mount's submounts pointer (which points to a map of all DirEntry instances and their respective Mount instances in the namespace), the task can reach all of the other mounted FileSystem instances under this namespace.

FsContext and Namespace

Figure 7. An FsContext has a Namespace, which has a Mount for tracking the root mount.

A NamespaceNode is useful for traversing paths in a Namespace. Each NamespaceNode tracks a Mount and its respective DirEntry.

However, because the same FileSystem can be mounted multiple times in a Namespace (for instance, at different mount point directories in the same parent FileSystem), multiple NamespaceNode instances may be necessary to track these different Mount instances that lead to the same underlying FileSystem.

NamespaceNodes instances

Figure 8. A FsContext has two NamespaceNode instances for tracking specific directories: root and cwd.

For instance, each FsContext contains, at a minimum, two NamespaceNode instances for tracking the following directories (see Figure 8):

  • The root directory
  • The current working directory (cwd)

Both NamespaceNode instances are used for looking up paths in this Namespace. A path starting with / (for example, /example/file1.txt) is looked up respective to the root NamespaceNode, and a path without a starting / (for example, file1.txt) is looked up using the cwd NamespaceNode.

A simple namespace

Figure 9. A simple namespace with a single (root) Mount.

The diagram in Figure 9 illustrates a namespace with a single Mount where the current working directory (cwd) also happens to be the root directory. The diagram in Figure 10 shows that the current working directory is changed to the example directory (for instance, cd example is run), which causes the entry pointer of the cwd NamespaceNode to be updated to the example directory.

cwd NamespaceNode

Figure 10. The current working directory is changed to the example directory in the namespace.

The diagram in Figure 11 shows a namespace where the Example FS is mounted to the Parent FS. Under this scenario, when cd example is run, the mount pointer of the cwd NamespaceNode is updated to a new Mount (labeled "Example Mount") that tracks the root directory of the Example FS. Using this new Mount, the task in the FsContext can seamlessly move from the Parent FS to the Example FS when traversing paths.

A namespace with mounted file systems

Figure 11. A namespace where the Example FS is mounted to the example directory of the Parent FS.

Symbolic links (or symlinks) are handled while walking NamespaceNode instances. If the task hits a symlink and asks for its path, the path of this symlink is resolved recursively from the NamespaceNode.