The filesystem hierarchies of operating systems have never been the most robust. They exist solely to provide a background for the actual operating system to run on top of. Because of that, they have evolved naturally over time in weird and occasionally detrimental ways. This blog exists to learn from the mistakes of previous filesystems and to attempt to come up with something better.
Sections will be split into logical components based on the type of files that would be contained in each directory, going from the toplevel to the deepest point of the layout. At the end, a visual layout of the filesystem will be put together to finalize the design.
Vocabulary
vendor - The distributor of the system. Commonly referred to on Linux as “distros”.
state - The difference between the running system and the vendor’s image.
The Vendor-User split
The first thing needed to be considered is how vendor and user files will be
separated. Different operating systems have taken different approaches to this.
The most common solution is to just give up and lump them both together. This
generally works fine as long as the user does not modify any vendor-provided
files, which leads to configuration drift and eventual breakage. Although,
lumping the two together loses out on the ablity to reset the system back to a
prestine state without a full reinstall. This has been attempted to be fixed
through a system called “hermetic-usr”, which puts all vendor files in /usr/
and all user and state files outside of it.
Hermetic-usr fails where not every directory from the vendor is put under
/usr/. Mount points like /boot/ or /efi/ lay on the root, which makes
sense from a logical stance, since if /usr/ is immutable, the two writable
mountpoints can not go underneith it. This falls apart when you try wiping the
system and hit those mount points. As such, it would make sense to contain
user files in a similar manner to vendor files. That way, the user directory
can be wiped with one run of rm instead of manually removing each directory
individually.
The layout we get from this naturally falls into three directories.
/Vendor/, which contains all vendor provided files./User/, which contains all user-modifiable files./State/, which contains all directories related to system state.
/Vendor/ and /User/ contain the same subdirectories, but /Vendor/ is
read only and /User/ is read-write. /State/ is also read-write, but it
should only be written to by programs and does not need manual user
intervention.
Sub-Directories
Since /Vendor/ and /User/ have the same subdirectories, each will only be
presented once. All the following directories would need to go under a
directory that separates them from one another by package. I considered
calling this directory Programs/, but it is possible that installed
packages may not be programs at all, so Packages/ makes more sense. Under
Packages/ would lie individual package directories. For example: if zsh is
available on the system, all its files would be located under Packages/zsh/.
There are a few different types of files that would be installed that need to be considered.
Binaries
In this system layout, for simplicity’s sake, I am considering all binaries to
be fully statically linked. This makes it easier to sandbox them through
limiting their access to the wider filesystem beyond their Programs/
sub-directory and config directory, which the layout design lends itself well
to.
Within the category of binaries, there are binaries that the user should be
able to run themselves and binaries that should be limited to services. This is
seen in modern Linux with /usr/libexec/ or /usr/lib/ for service binaries
and /usr/bin/ for user binaries. If a binary is used in a service but also
supports being run by a user, the binary should go in the user runnable
binaries directory.
Binaries would be stored under UserBinaries/ and ServiceBinaries/.
Configuration
Configuration is a hard problem to solve in this layout because some programs
want to read others’ configurations as well as their own. One solution would
be to duplicate configuration files across directories, or perhaps symlink
them. Another is to give programs access to configuration files within other
Programs/ directories. I’ll leave this to later iterations of the layout
since it is hard to find a satisfactory solution without seeing it in action.
Configuration would be stored under Config/.
Services
Services are an integral part of any system, so giving them their own directory is a must. All service files should go in this directory and NOT the directory for other data.
Services would be stored under Services/.
Other Data
Other data includes any non-user-readable files, such as prebuilt databases
or extra information that a program needs. These should not be anything that
the application expects the user to be able to edit, but does not exclude
files that a user could edit. For example: if a program has config.toml and
config.xml, and the program expects the user to edit config.toml and not
config.xml, config.toml would go in Config/ and config.xml would go
into Resources/.
Other data would be stored under Resources/
Persistent Data
Persistent data includes anything that should be expected to survive a system
reset. This includes user configurations for applications under System/.
Persistent data would be stored only under the User/ directory, within
Persist/, split into subdirectories per package.
Home Directory
This is a very opinionated decision. With this design, single-user desktop
operating systems are specifically targeted. As such, anything utilizing
multiple real user accounts are out of scope. This could be amended in the
future with multiple User/ directories, but currently little thought has been
put into this use-case.
The Home/ directory’s usage depends on whether it is under System/ or
User/. Under System/, Home/ represents the home directory of the root
account. Under User/, Home/ represents the home directory of the active
user. For the root account, System/Home/ should be pre-populated, since at
runtime it will not be modifiable.
Development Resources
Development resources, such as header files, libraries, or dynamic libraries
should only be installed under User/. This is because System/ should
consist solely of the base resources for the functionality of the system.
Development resources would be stored under Devel/. Headers would be stored
under Devel/Headers/[Lang]/. Libraries would be stored under
Devel/Libraries/[Lang]/. Dynamic libraries would be stored under
Devel/DynLibraries/[Lang]/. The separation by language exists to explicitly
portray what language a resource should be used by and to logically separate
the resources for developer use.
The State Directory
State/ is separate from User/ or System/, including only the resources
that represent drift from the vendor image, but are not specifically tied to
the user. User/ should be able to be wiped without requiring also wiping
State/.
Sub-Directories
The following are the directories included under State/. The full path will
be excluded, so keep that in mind.
Mount Points
Mount points shall be included under sub-directories of Mount/. For example,
the boot partition will always be mounted under Mount/Boot/.
Udev Managed Files
Anything managed by udev shall be located under Devices/.
Kernel Device Tree Mappings
Kernel device mappings shall be located under Objects/. (This is /sys/.)
Kernel State
The state of the currently running kernel shall be located under Status/.
(This is /proc/.)
Cache Location for Running Programs
Each program will get its own directory under Cache/, created as temporary
directories that will be lost on reboot. Applications should not store anything
persistent in this directory.
Wrapping Up
Hopefully, we’ve drafted a logical filesystem layout for a UNIX-like operating system. But, there are absolutely some usecases or problems that have yet to be solved. I hope to iterate on this over time and clean it up to the point that it could be theoretically used by a real distribution. I would like to thank Gobo Linux for the original inspiration for designing a layout. That’s all for now!
Layout
/
┣ Vendor
┃ ┣ Packages
┃ ┃ ┣ package1
┃ ┃ ┣ Config
┃ ┃ ┣ ServiceBinaries
┃ ┃ ┣ UserBinaries
┃ ┃ ┣ Resources
┃ ┃ ┣ Services
┃ ┗ Home
┣ User
┃ ┣ Packages
┃ ┃ ┗ userpackage1
┃ ┃ ┣ Config
┃ ┃ ┣ ServiceBinaries
┃ ┃ ┣ UserBinaries
┃ ┃ ┣ Resources
┃ ┃ ┣ Services
┃ ┃ ┗ Devel
┃ ┃ ┣ Headers
┃ ┃ ┣ DynLibraries
┃ ┃ ┗ Libraries
┃ ┣ Home
┃ ┗ Persist
┃ ┣ package1
┃ ┗ userpackage1
┗ State
┣ Cache
┣ Devices
┣ Mount
┃ ┗ Boot
┣ Objects
┗ Status