Advanced Namespace Tools blog

16 February 2017

Implementing /srv Namespaces Part Two

The previous two blog installments talk about the design of private /srv namespaces, and implementing the data structures and debugging their behavior without yet making use of them. To continue testing, I wanted to see if the data structures were being changed appropriately with the use of the new rfork flag. I had to add the flag definition statement to the main libc.h, and then I could compile this:

if(rfork(RFPROC|RFCSRVG) != 0)
execl("/bin/rc", "rc", "-i", 0);

Then, by execing that program from rc and observing the kernel debugging prints I had inserted to monitor the behavior of the new data structures, I could determine that a new sgrp was being created. I had now verified all the behaviors I could think of and the next step was changing the references to the original "srv" pointer variable to the process specific "sgrp->srvgrp" pointer. So, in fuctions like srvlookup():

-	for(sp = srv; sp != nil; sp = sp->link) {
+	for(sp = up->sgrp->srvgrp; sp != nil; sp = sp->link) {

There were about four other places that needed the same substitution. For testing and debugging purposes, I added something like this to the beginning of every function in devsrv:

print("srvinit: ");

The srvprocset() function is responsible for checking that up->sgrp exists, and creating it if not:

if(up->sgrp == nil){
	print("up->sgrp is nil in proc %s %ld", up->args, up->pid\n");
	up->sgrp = smalloc(sizeof(Sgrp));
	up->sgrp->ref = 1;

The combination of print statements revealed (pretty much as expected) that only pid 1 and processes calling srvclose() during pexit() ever met this condition. Furthermore, the system with /srv being generated through the processes' sgrp was behaving normally. Testing the full modification to allow rfork into a new /srv namespace only required removing some of the safety/debug code from srvprocset() - there had been a test for nil srvgrp, which was redirected back to the default "srv" pointer. Removing that meant the code would fall back to the basic devsrv logic which creates a new Srv structure if it finds nil as the srv pointer value, and with the new logic, this acts as the initial link in a new private /srv. With that fallback deleted, after exec-ing the rfork test program, ls /srv showed...nothing, just as hoped.

Rfork V in rc and Closesgrp Cleanup

With /srv namespaces working, it was time to add support in rc, which offers a built in rfork command with support for most of the flag-bits offered by rfork (2). The letter 'S' was already in use, so I chose 'V' and added the following lines to the buil-in rfork function in rc/plan9.c:

case 'V':
	arg|=RFCSRVG;  break;

Well, that was nice and easy. The next thing to do was fill in the blank closesgrp() function:

closesgrp(Sgrp *sg)
	Srv *sp;
	Srv *tsp;
	if(decref(sg) == 0){
		sp = sg->srvgrp;

Also needed was cleaning up all the unnecessary calls to srvprocset(), leaving only the ones in srvinit() and srvclose(). Also, there were plenty of debugging prints to delete, and the manpage for rc(1) and rfork(2) needed to be updated to show the V and RFCSRVG flag.

When all is said and done, less than a hundred lines of code need to be added or changed for kernel and userspace support for private /srv namespaces. The method of creating and testing the correctness of the data structures without changing the actual behavior of devsrv.c meant that I experienced only one kernel crash while developing the feature, and when the feature itself was enabled, it behaved correctly on the first boot.

Plan 9 Design - A Fine Wine

One of my motivations for writing this blog is to show the virtues of Plan 9, and provide examples of how the "composable tools" philosophy, that originates from Unix of the 70s and was refined by Plan 9, still combines simplicity, flexibility, and power. Rather than creating a new, special purpose mechanism to create and manage /srv namespaces with All New and Improved Code, I looked at the existing behaviors of the system (in particular how the #e /env kernel device is handled) and attempted to make the new feature follow its precedent.

As a result, explaining how it works is as simple as saying "rfork V sets RFCSRVG and gives you a clean /srv, just like how rfork E sets RFCENVG" and any experienced Plan 9 user will understand the idea and how to use it. Behind the scenes, it is just a few lines of C to set up process-specific pointers to a linked list of structures, simpler than a lot of K&R examples.