Masterpiece - Implementing a Layered Filesystem (Part Two)

A filesystem-layering approach for packaging and redirecting game files without making every install special.

vxlogo

Background

Software management generally includes some method of managing files and their respective paths. Several approaches to this exist, and solutions greatly depend upon if software is expected to be updated as a whole (traditional), updated with modules (plugin-based), updated incrementally (e.g. diffs, volumes), or a mix of all types.

Some solutions go far enough to include custom filesystem layouts within the software for maximum control.

Because we are implementing a system that should work with all (most) software, we have a few constraints:

Initial Design Considerations

While we could create a virtualized filesystem layout and cache it in our application memory, this comes with a few constraints:

Console OSes have a few solutions based on their designs of these systems, they:

Although we won’t borrow the kernel-driven approach, modern Operating systems have some of these components, they:

So - merging these ideas, we may get something like this:

fs

Enter Smoothie

For this, I created a compositing library to take arbitrary images and paths backed by a configuration, and create a layered filesystem of paths and various changes.

Smoothie takes a configuration file (called a map), mounts images of various formats (iso, zip, vhd, vhdx), and can make arbitrary changes (such as deleting files in a layer) and replacing others by way of leveraging the host Operating Systems symbolic linking. A map may look something like this:

MAP;example.zip;C:\
MAP;exiso.iso;D:\
MAP;windows.vhdx;C:\Windows
LINK;somefile_example.txt;C:\app\somefile.txt

The working root is then divided into multiple parts:

While a lot of the compositing logic is cross-platform, image mounting and manipulation is not. As a result, several approaches are necessary for cross-platform support. On Windows, we have VirtDisk.dll that allows us to programmatically mount and manage ISO, VHD, etc.. On Linux, we have libguestFS.

The workflow, however, is essentially the same:

When done, the process follows a fairly predictable flow:

There are working examples written in C++ and .NET 5.0 (C#) for smoothie:

https://github.com/batteryshark/libsmoothie

https://github.com/batteryshark/Smoothie.NET

A working example map might be something like this for a Windows app:

sd4

Then, our working root would look like this:

sd1

And the map root for C:

sd2

Finally, the mount directory showing everything that was mounted:

sd3

Additional Considerations

Now, we can’t just tell a process that /path/to/root/thing is /root/thing, right? Further, we can’t just expect a process to like the fact that we’re mounting packages as read-only and stick to writing within our composite filesystem… we need some kind of redirection to happen from within the process. Let’s get to that next!

October 2021