Launching Filesystems on Fuchsia

Filesystems on Fuchsia are regular programs that run in userspace. They consume a block device and a set of options, and produce an export directory with several protocols, first and foremost a fuchsia.io.Directory representing the root of the filesystem at /root.

There are currently two ways to launch a filesystem. One way, the old way which is being deprecated, is to launch the raw filesystem binary, and the other way, which filesystems are moving to, is to launch as a component via the component framework. Both of these methods are supported by fs_management libraries in C++ and Rust (the Rust implementation is pending - https://fxbug.dev/42178109).

No matter how the filesystem is launched (the old way or the new way), all our platform filesystems have the same structure to the export directory. The basic entries are -

/diagnostics
/fuchsia.fs.Admin
/root

diagnostics provides the inspect tree implementation, which has a bunch of statistics and metrics. fuchsia.fs.Admin provides the ability to shut down a filesystem instance. root is the root of the filesystem. Other platform filesystems may include additional entries. For example, blobfs provides /fuchsia.update.verify.BlobfsVerifier.

Launching filesystems with fs_management

Filesystems are launched using the fs_management::Mount, which takes a block device file descriptor, an optional mount path, the disk format, options, and a launch callback. Mount returns an RAII MountedFilesystem object with which you can Unmount and get access to the export directory.

The block device should be opened as read-write unless you configure the filesystem to be in read-only mode. One option for block device paths to open is in /dev/class/block, which is an enumerated list of all the block devices Fuchsia knows about (starting at 000 and counting up). It's populated in the order that the drivers are bound, which is not consistent across boots. The other option is to use the topological path, which will depend on the drivers the storage uses (but is otherwise consistent). Both of these can be found using lsblk on the command line. A couple of examples -

/dev/sys/platform/00:00:2d/ramctl/ramdisk-0/block
/dev/sys/platform/pci/00:1f.2/ahci/sata0/block

If you are launching a filesystem on a ramdisk, you can use the ramdisk functions to get the ramdisk path.

The mount_path is expected to be a non-existing path in your namespace. fs_management uses fdio_namespace_bind to place the root of the filesystem at that path in your local namespace. Note: This path is process local! Only your process sees the namespace, and a new one is created for every process. See //docs/concepts/process/namespaces.md for more information.

The options struct, fs_management::MountOptions, contains a union of all possible filesystem configuration. That is, not all filesystems support all the options. Make sure that the filesystem you are launching supports the option you are using, or else it may fail to launch.

If both component_child_name and component_collection_name are provided, then fs_management will assume the component is a dynamic child in the realm. It attempts to connect to it‘s exposed directory via fuchsia.component.Realm, and if it fails because it can’t find a component with the given name, it attempts to launch a new instance, using a component url based on the disk format.

If only component_child_name is provided, then fs_management will assume the component is a static child in the realm. It attempts to connect to it's exposed directory via fuchsia.component.Realm.

A shard is provided for cml files to include if they plan to launch filesystems as dynamic children at //src/storage/lib/fs_management/client.shard.cml. The component_collection_name using this shard is fs-collection.

Launching a filesystem using regular processes -

fbl::unique_fd device_fd(open("/dev/class/block/001", O_RDWR));
ASSERT_TRUE(device_fd);
fs_management::MountOptions options;
auto fs = fs_management::Mount(std::move(device_fd), fs_management::kDiskFormatMinfs, options);
ASSERT_EQ(fs.status(), ZX_OK);
auto data = fs->DataRoot();
ASSERT_EQ(data.status(), ZX_OK);
auto binding = fs_management::NamespaceBinding::Create("/fs", std::move(*data));
ASSERT_EQ(binding.status(), ZX_OK);
// Now /fs points at the root of the filesystem.

Launching a filesystem using a component collection -

fbl::unique_fd device_fd(open("/dev/class/block/001", O_RDWR));
ASSERT_TRUE(device_fd);
fs_management::MountOptions options {
  .component_child_name = "minfs",
  .component_collection_name = "fs-collection",
};
auto fs = fs_management::Mount(std::move(device_fd), fs_management::kDiskFormatMinfs, options);
ASSERT_EQ(fs.status(), ZX_OK);
auto data = fs->DataRoot();
ASSERT_EQ(data.status(), ZX_OK);
auto binding = fs_management::NamespaceBinding::Create("/fs", std::move(*data));
ASSERT_EQ(binding.status(), ZX_OK);
// Now /fs points at the root of the filesystem.

Launching a filesystem as a process

This section has a detailed description of how fs_management launches platform filesystem processes. It's intended for filesystem developers - if you just want to mount a filesystem, in code, see the previous section.

Platform filesystem processes take 2 startup handles and a handful of command line arguments. The two startup handles are the server end of the export directory (PA_DIRECTORY_REQUEST), and a handle to the block device (PA_HND(PA_USER0, 1)).

The export directory should have the structure described at the beginning of this document.

The block device should be a handle to something that speaks fuchsia.hardware.block.Block. Most block devices speak a handful of other protocols as well.

Filesystems can use protocols from their namespace, but they should fail gracefully if they are not there. This is because many test environments don‘t construct consistent namespaces for the filesystems they run. The most common production environment is fshost, which copies all it’s services to filesystems it launches, so using anything it uses in it's cml is probably fine.

Launching a filesystem as a component

This section has a detailed description of how fs_management launches platform filesystem components. It's intended for filesystem developers - if you just want to mount a filesystem, in code, see the previous section.

There is currently one filesystem that supports being launched as a component, blobfs. The cml file for blobfs is in //src/storage/blobfs/bin-component/meta/blobfs.cml.

When a filesystem component is launched, it starts in a partially configured state. They serve one protocol - fuchsia.fs.startup.Startup. This is served from the path /<fs_component>/svc/fuchsia.fs.startup.Startup. Filesystems need two things to run - a set of options and a block device handle. This protocol provides methods for Start, as well as Format and Check, which take the block device and a set of options as arguments.

Start returns once the filesystem is launched. Before this point, it's expected that filesystems queue incoming open requests to be processed once Start is called. In blobfs, this is done by selectively calling Serve on parts of the export directory at a time. If a Start call fails, fs_management will attempt to destroy and restart the lifecycle component.

Filesystems that are launched as components are also expected to consume an extra startup handle, PA_LIFECYCLE. This handle is the server end of a fuchsia.process.lifecycle.Lifecycle protocol, which the component framework can use to send shutdown requests.