London and Reiser report about porting the shell that “it required by far the largest
conversion effort of any supposedly portable program, for the simple reason that it is not
portable.” By the time of SysIII this is greatly improved, but also in porting the SysIII
user land it was the most complex of the set so far.
There were three aspects that I found noteworthy:
1. London/Reiser apparently felt strongly about a property of casts. The code argues that
casting an l-value should not convert it into a r-value:
<quote from "mode.h">
/* the following nonsense is required
* because casts turn an Lvalue
* into an Rvalue so two cheats
* are necessary, one for each context.
*/
union { int _cheat;};
#define Lcheat(a) ((a)._cheat)
#define Rcheat(a) ((int)(a))
<endquote>
However, Lcheat is only used in two places (in service.c), to set and to clear a flag in a
pointer. Interestingly, the 32V code already replaces one of these instances with a
regular r-value cast. So far, I’d never thought about this aspect of casts. I stumbled
across it, because the Plan 9 compiler did not accept the Lcheat expansion as valid C.
2. On the history of dup2
The shell code includes the following:
<quote from “io.c”>
rename(f1,f2)
REG INT f1, f2;
{
#ifdef RES /* research has different sys calls from TS */
IF f1!=f2
THEN dup(f1|DUPFLG, f2);
close(f1);
IF f2==0 THEN ioset|=1 FI
FI
#else
INT fs;
IF f1!=f2
THEN fs = fcntl(f2,1,0);
close(f2);
fcntl(f1,0,f2);
close(f1);
IF fs==1 THEN fcntl(f2,2,1) FI
IF f2==0 THEN ioset|=1 FI
FI
#endif
}
<endquote>
I’ve check the 8th edition source, and indeed it supports using DUPFLG to signal to dup()
that it really is dup2(). I had earlier wondered why dup2() did not appear in research
until 10th edition, but now that is clear. It would seem that the dup of 8th edition is a
direct ancestor to dup() in Plan 9. I wonder why this way of doing things never caught on
in the other Unices.
3. Halfway to demand paging
I stumbled across this one because I had a bug in my signal handling. From early days
onwards, Unix supported dynamically growing the stack allocation, which arguably is a
first step towards building the mechanisms for demand paging. It appears that the Bourne
shell made another step, catching page faults and expanding the data/bss allocation
dynamically:
<quote from “fault.c”>
VOID fault(sig)
REG INT sig;
{
signal(sig, fault);
IF sig==MEMF
THEN IF setbrk(brkincr) == -1
THEN error(nospace);
FI
ELIF ...
<endquote>
This was already present in 7th edition, so it is by no means new in 32V or SysIII -- it
had just escaped my attention as a conceptual step in the development of Unix memory
handling.