Unix Userland should be replaced

Software installation and configuration is way too error prone, especially when it comes to setting up development environments and build processes. Many suppose that the way to solve (or at least mitigate) this problem is to simply demand higher-quality software. When a piece of software fails to account for odd configurations, that is a bug in the software, right? Shouldn’t the developers be held responsible? Well, to a point, of course. However, I’m not optimistic that lambasting lazy developers is a fruitful path. Besides, the number of possible (and often unexpected) configurations developers must account for has grown to byzantine levels.

The real root of our problems — or at least, the source we can realistically address — is not developer laziness but rather the complexity of our underlying platforms. We’re building programs upon platforms which themselves have too much configuration state and which encourage our programs to in turn proliferate even more configuration state. The more configuration state we have, the more potential for complex interactions, and the more fragile everything becomes.

Happily, it turns out that a huge chunk of all configuration in our systems concerns two things:

1. hooking things together

In a simple world, everything would have a wholly unique name that never changes…but that is not the world our computers live in. Misplaced, misnamed, or missing files, directories, registry settings, environment variables, and ports — how many headaches has every computer user and programmer suffered from references not matching up with the the right referent?

2. security permissions

Also in a simple world, our platforms and programs wouldn’t have to account for malicious intent. Sadly, we’re mired in user accounts, user groups, file permissions, process privileges, and firewalls, not to mention app-level security, such as database user accounts and permissions.

Now what if I told you we could get rid of most of these headaches? All it would take is a new operating system —

*fervent booing from the audience*

— well, not necessarily a whole new operating system: we could probably take the bulk of the existing Linux kernel and tweak its surface level abstractions, e.g. processes and the file system. In other words, we just need to create a new Userland. Here’s what that new Userland could look like:

dependency management

First off, the system has a kernel-level notion of dependency management: a list is maintained of installed packages (programs and libraries), each identified by a machine-independent id (probably a UUID), such that the dependencies between packages are explicitly known during installation and removal. For the sake of internal-use-only programs and in-development programs, a range of id’s is reserved for private use.

Packages are also known by a version id (probably a SHA2 hash of their unpacked content) such that multiple versions of a package may live side-by-side on the same system. A package may optionally specify the particular versions of its dependencies.

The installed package list denotes which programs have admin privileges. Only admin programs can add/remove other packages and forcibly stop/reset other programs.

Supported platform ABI / system calls are also known by ids, and each package declares which of these it requires.

interprogram communication

Programs communicate directly through a request-response mechanism (not unlike an http request, except much simpler). A program sends a request to another program by its id. Through these requests, programs may share handles to files, pipes, and shared memory.

While admins can run multiple instances of programs, the requests to these instances always end up in a shared queue seen by all instances. The program sending the request only ever gets back one response, and that response is from no instance in particular. The implication here is that a program expected to be instanced must account for coordination amongst multiple instances of itself.

Every installed program is always running — or at least, ‘running’ in the sense that a program may at any time send messages to any other program installed on the system. Most installed programs, however, keep themselves in a dormant state, paged out to disk and not consuming CPU time. A typical user-facing program, like a game, for example, does its business when receiving a ‘start’ request and cleans itself up to go dormant when receiving a ‘stop’ request.

The rationale here is that starting and stopping program instances is another kind of system state that can lead to configuration problems. Programs should treat other programs like network services with fixed addresses. Sometimes requests to services timeout because the service is down or overwhelmed, but our programs should be designed to cope with such failures.

files

An installed program has its own private hierarchy which cannot be seen by any other program. A program can pass a file handle to another program via a request, allowing that other program to read and (optionally) write the same file, but the file remains the property of its original owner.

As mentioned in passing above, shared libraries are looked up by unique id’s and version id’s rather than any file path.

But what about user files? It’s one thing for code, resource, and internal config files to be kept private to individual programs, but user files generally need to be accessed from many programs, e.g. a word processor document should generally be accessible from any text editor.

The solution is a ‘file server’ program. Much like a display server on Linux controls the display, the file server stores and provides access to all user files. Within the file server, files are not organized by name and directory but rather are known by file id’s and version id’s (much like packages within the system) and marked with metadata fields. The file server textually indexes these fields and any textual content of the files themselves.

Though search is the primary means of finding and organizing files, sometimes we have good reason to group user files. For this purpose we have directories. Unlike conventional directories, these directories do more than just list files and other directories but also may store the relationships between these files and directories, such as their relative order (e.g. a directory of audio files may list them in the fixed order they appear on an album). Directories may have their own metadata, like files.

THOU SHALT NOT SPECIFY FILES OR DIRECTORIES BY PATH. If you want to reference a file or directory, use its id, not a ‘path’ relative from some directory. In general, you should avoid program wiring and logic relying upon expected directory structures because they constitute insidious configuration state.

Files and directories are treated as if they are pseudo-immutable: when writing to an open file, you are actually creating a new separate version of the file; when the newly written file is closed, its new hash is computed so that this new version of the file can be distinguished from the version from which it is derived.

Note that files in the file server belong to no particular user. There is no concept of user permissions with these files. If you don’t trust other users with your locally stored personal files, don’t share the machine with them.

registry server

As much as we’d like to avoid system configuration, we can’t avoid it entirely. Enter the registry server, a program that stores all program and system configuration, including the set of user accounts. Other programs query the registry via the same request-response mechanism used for any other IPC.

To log in as a user is to simply update the ‘current user’ in the registry. Upon login, the registry notifies all programs so that they may change their behavior to reflect the newly logged-in user’s preferences as stored in the registry. (All programs that deal with registry settings should expect this sort of message and act accordingly.)

Like the Windows registry, the registry server stores key-value pairs, but unlike Windows, this registry is non-hierarchical.

Another difference is that there is a separate key-space for every program-user combination, e.g. key ‘foo’ for user Bob and program 9 is different from key ‘foo’ for user Carol and program 9. By default, a key-value pair is visible only to its own program and user.

The admin is represented with an account in the registry. Most truly global settings are stored as key-values belonging to the admin. Hardware configuration settings, for example, are stored in admin keys.

Unlike the windows registry, the keys are always untyped blobs of bytes (up to some maximum size…perhaps 4096?).

When a program is uninstalled, its registry keys are by default left in the repo; when reinstalling a program, the user can elect to flush the pre-existing keys or keep them as is.

Importantly, the registry is only for configuration, not mere information. If a piece of data is not something a user or admin would conceivable wish to modify in order to change program behavior, it’s not configuration. So compared to the Windows registry, the registry server should contain much less data. How many configuration options should our software require anyway?

networking

Rather than relying upon well-known ports or manually-configured ports, a discovery service tells other systems which port(s) a program is mapped to by its machine-independent id. (This discovery service always runs on the same fixed port.) So for example, we can ask this service which port (if any) is currently mapped to the program with the machine-independent id 7.

For cases where a single program offers multiple ports for distinct services, they should all have their own machine-independent id’s. (This means that a single package may actually be known by multiple id’s.)

recap

So now, consider what the system as described above ditches from traditional Unix:

  • the shared file hierarchy and file permissions
  • user accounts and groups
  • the process hierarchy (parents, children, zombies, sessions, jobs, environment variables…)

Each program is guaranteed to have its dependencies installed and running, and multiple versions of programs/libraries live side-by-side transparently. Programs communicate and coordinate only through requests, not through side-channels like environment variables or config files. System security is reduced to the simplest model: regular programs keep to themselves, and only a select few programs have admin privileges.

In theory, a Unix system gives programmers a powerful environment by giving them complete control over how everything is wired together. In practice, however, Unix burdens programmers with mountains of fragile config to manage, wasting countless programmer hours. (Windows, of course, pervasively commits the same sins, but at least core Windows elements tend to be uniform across all systems instead of varying by distribution and system admin whims.)