From 0b200c1d0a30681e4c48e9365a852b7727657dc6 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Wed, 1 Sep 2021 18:48:17 -0400 Subject: Publish "V6 pwd" --- www/text.causal.agency/025-v6-pwd.7 | 326 ++++++++++++++++++++++++++++++++++++ www/text.causal.agency/Makefile | 1 + 2 files changed, 327 insertions(+) create mode 100644 www/text.causal.agency/025-v6-pwd.7 diff --git a/www/text.causal.agency/025-v6-pwd.7 b/www/text.causal.agency/025-v6-pwd.7 new file mode 100644 index 00000000..70e441ad --- /dev/null +++ b/www/text.causal.agency/025-v6-pwd.7 @@ -0,0 +1,326 @@ +.Dd September 1, 2021 +.Dt V6-PWD 7 +.Os "Causal Agency" +. +.Sh NAME +.Nm V6 pwd +.Nd deciphering old code +. +.Sh DESCRIPTION +We were talking about +.Xr wall 1 +on IRC +and how long it had been annoying users. +My manual page says +.Xr wall 1 +appeared in +.At v6 , +which means that +.Xr wall 1 +has been annoying users for 46 years! +. +.Pp +The Wikipedia page links to the source for +.At v6 , +so I was curious to see how the very first +.Xr wall 1 +was implemented. +It's not that surprising, +except that it is hardcoded +to handle only 50 logins, +and it forks to write to each tty, +waiting one second between each. +I think the forking must be to avoid +any of the terminals being opened +from becoming the controlling terminal +of the original +.Xr wall 1 +process. +. +.Pp +Then I started looking +at some of the other source files +and found the implementation of +.Xr pwd 1 , +which was surprising. +There's no +.Xr getcwd 3 +function +(the earlier form of which, +.Xr getwd 3 , +appeared in +.Bx 4.0 ) , +so +.Xr pwd 1 +has to figure out +the path to the working directory itself. +It took me a while to figure out how it works. +. +.Pp +To make it easy to talk about, +I'm just going to include the whole thing here: +.Bd -literal +char dot[] "."; +char dotdot[] ".."; +char root[] "/"; +char name[512]; +int file, off -1; +struct statb {int devn, inum, i[18];}x; +struct entry { int jnum; char name[16];}y; + +main() { + int n; + +loop0: + stat(dot, &x); + if((file = open(dotdot,0)) < 0) prname(); +loop1: + if((n = read(file,&y,16)) < 16) prname(); + if(y.jnum != x.inum)goto loop1; + close(file); + if(y.jnum == 1) ckroot(); + cat(); + chdir(dotdot); + goto loop0; +} +ckroot() { + int i, n; + + if((n = stat(y.name,&x)) < 0) prname(); + i = x.devn; + if((n = chdir(root)) < 0) prname(); + if((file = open(root,0)) < 0) prname(); +loop: + if((n = read(file,&y,16)) < 16) prname(); + if(y.jnum == 0) goto loop; + if((n = stat(y.name,&x)) < 0) prname(); + if(x.devn != i) goto loop; + x.i[0] =& 060000; + if(x.i[0] != 040000) goto loop; + if(y.name[0]=='.')if(((y.name[1]=='.') && (y.name[2]==0)) || + (y.name[1] == 0)) goto pr; + cat(); +pr: + write(1,root,1); + prname(); +} +prname() { + if(off<0)off=0; + name[off] = '\en'; + write(1,name,off+1); + exit(); +} +cat() { + int i, j; + + i = -1; + while(y.name[++i] != 0); + if((off+i+2) > 511) prname(); + for(j=off+1; j>=0; --j) name[j+i+1] = name[j]; + off=i+off+1; + name[i] = root[0]; + for(--i; i>=0; --i) name[i] = y.name[i]; +} +.Ed +. +.Pp +First, some syntax trivia: +it seems you don't need +.Sy = +to give globals values. +I guess that makes sense. +I also noticed that +it avoids giving +.Va inum +and +.Va jnum +the same name. +I think that's because in old C, +struct field names all shared the same namespace. +The last difference I noticed +is the operator +.Sy =& +rather than +.Sy &= . +Honestly I think the former makes more sense, +but I can see that the one we have now +is less ambiguous. +. +.Pp +To get +.Fn prname +and +.Fn cat +out of the way, +it's building up a path from the bottom. +At first I thought it must be +starting at the end of its buffer +and moving back as it adds components, +but no, +it moves the entire path-so-far over +every time it adds a new component +onto the front. +.Fn cat +is just a bunch of manual string copying. +It also gives up +if the new component +would make the path longer than 511 characters. +Fair enough. +. +.Pp +So how does it build up the path? +The loop in +.Fn main +first calls +.Xr stat 2 +on the current directory +.Pa \&. +in order to get its inode number. +I love that +.Vt struct statb +is just declared at the top of this file. +Clearly this code predates the C preprocessor. +. +.Pp +It then opens the parent directory +.Pa .. +and reads directory entries from it. +The inner loop is looking for +a directory entry with the same inode number +as the current directory, +to figure out what the current directory is called. +Curiously, +it reads 16-byte directory entries, +despite declaring a larger struct. +The preprocessor can't be invented soon enough. +. +.Pp +Once it finds the matching directory entry, +it adds the name of the entry +onto the front of the path, +changes directory to +.Pa .. +and starts over. +It stops when the current directory +has an inode number of 1, +which must be the root of a file system, +but then it does something else. +It took me a while to decipher what +.Fn ckroot +is doing. +. +.Pp +The loop in +.Fn main +stops when it gets to the root +of a file system, +but that's not necessarily +.Pa / . +I think what +.Fn ckroot +is doing is trying to figure out +where that file system is mounted. +It starts by checking the device number +that the current directory is on. +Or really it calls +.Xr stat 2 +on the name of the directory entry that +.Fn main +just found, +which I think must be +.Pa \&. +at this point anyway since it's at a root. +. +.Pp +Anyway, +it then changes directory to and opens +.Pa / +and starts reading directory entries from that, +calling +.Xr stat 2 +on each of them +and checking for a matching device number. +I think this implies that file systems +can only be mounted in +.Pa / +and not at any lower level, +at least not if you want +.Xr pwd 1 +to understand it. +I'm not sure what the check for +an inode number of 0 is skipping over +in this loop. +Some kind of special entry in +.Pa / +perhaps. +. +.Pp +Once it finds an entry +with a matching device number, +it checks the flags +to make sure the entry is a directory. +It does so with hardcoded constants, +but it seems they haven't changed +in all these years. +According to +.Xr stat 2 , +040000 is +.Dv S_IFDIR . +The number of file types +clearly has grown since then though, +since +.Dv S_IFMT +is now 0170000 rather than 060000. +. +.Pp +I think the reason it checks +that the entry is a directory +is because if it actually is +on the root file system already, +then any regular file +would have a matching device number. +If the entry is indeed a directory, +it then checks if the entry is +.Pa \&. +or +.Pa \&.. , +which indicates that it really is already at +.Pa / . +If it's not, +it adds the mount point that it found +to the front of the path. +. +.Pp +Finally, +it prints +.Pa / +followed by the path it built up. +If it failed at any point before that, +it would print the path it had built so far +with no leading +.Pa / . +Better than nothing! +. +.Pp +So that's how I think +.Xr pwd 1 +works in +.At v6 . +It was a fun puzzle to work through, +and it was interesting to see +the assumptions it makes. +How simple things were back then... +. +.Sh SEE ALSO +.Lk https://minnie.tuhs.org/cgi-bin/utree.pl?file=V6 +.Pp +.Pa pwd.c +appears in +.Pa V6/usr/source/s2 . +. +.Sh AUTHORS +.An june Aq Mt june@causal.agency +.Pp +I regret saying in two previous posts +what I planned to write next, +because this is still not that. diff --git a/www/text.causal.agency/Makefile b/www/text.causal.agency/Makefile index a9330045..afe07a14 100644 --- a/www/text.causal.agency/Makefile +++ b/www/text.causal.agency/Makefile @@ -27,6 +27,7 @@ TXTS += 021-time-machine.txt TXTS += 022-swans-are-dead.txt TXTS += 023-sparse-checkout.txt TXTS += 024-seprintf.txt +TXTS += 025-v6-pwd.txt all: ${TXTS} -- cgit 1.4.1