Namespace lifting
The usual model for using Plan 9 namespaces is to have a single leader
process construct a namespace out of resources known to be
needed by it or its kids. The kids assume that their dependencies are
available at known locations and will often fail to run entirely in their
absence, rather than trying to bring in the missing pieces themselves.
An example of this is the root namespace constructed by
the boot process that mounts the network stack on /net
and gets
inherited by descendant namespaces. All networking programs
simply expect the network resource there.
The graphical user environment follows a similar model. When a user logs in a new namespace is forked of the root namespace and their profile script is run by a shell within this namespace. The profile script’s job is to mount and bind all the resources that a user might expect to need during a terminal session: a plumber, mail file server, factotum, and so on. Rio is run on top of this namespace as the last thing.
Subsequently, windows created by rio inherit a copy of this template namespace through forking. Forking is the only namespace operation directly provided by rio. In particular, rio doesn’t provide a way to modify its namespace.
This can still be achieved by an unwieldy mechanism provided by plumber’s “Local” rule. The Local rule is a command backdoor into rio’s namespace and can be used to modify it.
I propose an alternative technique based on pin(1)
, a pinned I/O shell.
This idea is stole…cough inspired! by mycroftiv’s hub(1)
workflows.
With pin we can “pin” a fully capable interactive shell running in the same namespace as rio, providing a familiar way of interaction. The way to do this follows.
Grab a copy of mq(4) and pin(1), both available in the same git repo:
git/clone git://src.a-b.xyz/mq && cd mq && mk install
Pin a conveniently named rc right before starting rio, perhaps in your profile:
[plumber, upas/fs, etc.]
pin -c rio0
rio
With rio running we can create a window and attach to the pinned shell. For lack of a better name we’ll call this a control shell:
pin -a rio0
To show that we can modify rio’s namespace let’s bind something an observe what happens. Run in the control shell:
bind /sys/src /n/src
Create a new window and check that the system’s source code is indeed bound to a new name /n/src:
ls /n/src
Careful readers will have guessed that this didn’t change the namespaces of windows that were created prior to changing rio’s namespace. Remember that rio creates (almost) identical copies of its namespace for every new window, but these are in fact different entities independent of each other.
This same trick can be applied in other situations. An example that comes to mind is providing access to a namespace being exported over the network.
Let’s start by exporting an empty mountpoint, which we’ll later populate using namespace operations from our control shell.
Firstly, we’ll need to use srvfs(4) to create a persistent mountable namespace which we’ll mount in the network listener and export with exportfs(4). You might have thought that we could use exportfs(4) directly but that way we’d be actually exporting a different namespace for each connection.
Run
pin -c exportctl
srvfs export /n/export
This will create a /srv/export
pipe “containing” our namespace.
Next we’ll create a listener to export this pipe to network clients.
Exportfs with the -S flag can be used for the task:
aux/listen1 tcp!*!999 /bin/exportfs -S /srv/export
Let’s see if we can connect, run the following:
srv tcp!your.ip.or.name!999 export /n/export
This should mount our directory to /n/export on the client side. Run
ls /n/export
and assert that it’s empty.
Now let’s change the exported namespace using our exportctl
pin
on the server machine.
pin -a exportctl
bind /usr /n/export
Go back to the client machine and run ls /n/export
again, this time
user directories should show up.
That’s it folks. I’d like to hear if you find some other tricks to do with this.