# Plan 9.r post 01/B46A - 9front, qemu, lua Tags: [[pub/@9rhiz]] [[pub/@English]] $[[pub/9rhiz-disc]] A quick disclaimer. It is basically guaranteed that I am doing it wrong. Plan9 is different from the POSIX stuff I've been doing all my live. My assumptions will be wrong. So this is a way of doing it, definitely not the canonical one. ## Goal My goal is to keep all my shortcuts, custom keyboard layout and my modal text-editor until I have setup 9front and I am ready to switch to rio and acme for good. My brain runs to a very high degree on muscle memory and habits. If I were to switch back and forth between acme and my model text-editor, my brain would go completely bananas. Read more about my motivation [[pub/B45D]]{in the last post}. ## Prerequisites We download `9front-*.amd64.qcow2.gz` from [https://9front.org/iso](https://9front.org/iso). I first wanted to properly install 9front, but everything is already on a writable file-system in the qcow image. By scripting qemu I can easily replace or update the image. I propose that you script it yourself. ## Qemu So we start 9front like this: ```shell » export N9F_IMG=9front-11554.amd64.qcow2 » export N9F_EXP_9FS=hostfwd=tcp:127.0.0.1:1564-:564 » export N9F_EXP_RC=hostfwd=tcp:127.0.0.1:4242-:4242 » qemu-system-x86_64 -enable-kvm \ -smp $(getconf _NPROCESSORS_ONLN) \ -cpu host -m 1024 \ -device virtio-net-pci,mac=00:20:91:37:33:77,netdev=net0 \ -netdev id=net0,user,$N9F_EXP_9FS,$N9F_EXP_RC \ -device virtio-scsi-pci,id=scsi \ -drive if=none,id=vd0,file=$N9F_IMG \ -device scsi-hd,drive=vd0 \ -display none \ -monitor none \ -serial stdio ``` The following line defines a virtio network device with the id `net0`. I have no idea why we are setting a mac-address. Everyone does it and a bit of cargo-cult was easier than to experiment with the mac-addresses, so here we go: `-device virtio-net-pci,mac=00:20:91:37:33:77,netdev=net0` Then we use `-netdev id=net0,user,$N9F_EXP_9FS,$N9F_EXP_RC` to define a user-space networking stack on top of that device. We forward the ports 564, which is `9fs`. We also forward port `4242`, which is my default port for anything; in this case we will forward `rc` the shell of plan9. ```shell » export N9F_EXP_9FS=hostfwd=tcp:127.0.0.1:1564-:564 » export N9F_EXP_RC=hostfwd=tcp:127.0.0.1:4242-:4242 ``` I skip over setting the storage-device. Next we disable the display aka graphics card emulation. `-display none` We disable the monitor redirection as we don't need it. `-monitor none` But we want the serial console to be on `-serial stdio`. By default qemu would attach to the current pty, which means the file-descriptors of stdin/stdout would be in all kinds of strange modes that are good for someone typing but not good for scripting. Checkout `man termios` and there `raw mode` or `cfmakeraw()`. I used that for my [typewriter-emulation](https://git.sr.ht/~rhizoome/typewriter) but I have already forgotten what it all means. ## 9front When we start 9front will ask us which device it should use as `root` partition. `bootargs is (tcp, tls, il, local!device)[local!/dev/sd00/fs]` We very kindly respond with `enter`, because it detected the `root` partition correctly. `user[glenda]:` We select user `glenda` who is presumably in all the right groups. ```shell term% ip/ipconfig term% cat /net/ndb ip=10.0.2.15 ipmask=255.255.255.0 ipgw=10.0.2.2 sys=cirno dns=10.0.2.3 ``` 9front will present the rc shell to us and we call `ip/config`, which will run dhcp and request an IP from qemu's user-space networking. We can cat `cat /net/ndb` to check if it worked. ## Using it from Linux ```shell term% aux/listen1 -t tcp!*!564 exportfs -r /sys/src & term% echo 'rc -i >[2=1]' > /tmp/term listen started term% aux/listen1 -t tcp!*!4242 rc /tmp/term & listen started term% ``` `aux/listen1 -t proto!bind!port command` means that aux/listen1 listens on `proto` and binds to the IP `bind` on port `port`. When a client connects, it runs the `command` and forwards input and output to the client. The command `aux/listen1 -t tcp!*!564 exportfs -r /sys/src &` therefore listens on tcp 564 and exports the 9front source at `/sys/src`. We can mount this using the built in `9fs` support of linux, but it is very disappointing. ```shell » doas mount -v -t 9p -o rw,trans=tcp,port=1564,noextend 127.0.0.1 mnt mount: 127.0.0.1 mounted on /home/alter/mnt. ``` Whatever we do - I tried all the options in that linux-module - we get a read-only mount. This is very probably one of the instances where I am holding it wrong. But if I mount that file-system in 9front it works perfectly. I looked at the calls linux vs 9front do and it seems linux is always slightly off compared to 9front. Either `noextend` or `version=9p2000.u` should make the linux-module talk proper plan9 `9fs`, but either 9front doesn't talk proper plan9 `9fs` or linux doesn't. ## Plan9port to the rescue, almost ```shell » doas apk add plan9port » /usr/lib/plan9/bin/9pfuse 'tcp!127.0.0.1!1564' mnt ``` We install plan9port, make sure fuse is loaded and working; we get a read-write mount that is also slightly disappointing: While linux had a perfectly faithful read-only mount, plan9port gives us a somewhat lossy read-write mount: It doesn't list all the files. But we can open and edit the files it doesn't show, so for now this is good enough. ## Nuts shell Since my brain is used to some kind of unix-shell since over 30 years, it goes nuts with the GUI version of `rc`. I will try to get used to native rc, but for now we just fix it. `echo 'rc -i >[2=1]' > /tmp/term` This command creates an `rc` that only uses stdin and stdout. I love the `rc` syntax - `>[2=1]` is a very expressive syntax for saying we want to merge stderr and stdout. `aux/listen1 -t tcp!*!4242 rc /tmp/term &` Next we listen on the geek-protocol port 4242 [^1] and we start our instrumented rc when a client connects to it. `socat READLINE,history=$HOME/.rc_history TCP4:127.0.0.1:4242` On the host we connect to the with `socat` and `READLINE`. And just like that we ignored all the good things about plan9 and assimilated it into our borg collective. [^1]: I just invented that name. ## How to script it I use my own terrible implementation of an interface that is a bit like pythons Popen. The part where I prevent IO deadlocks in single-threaded lua, is actually quite good, but everything else isn't. You find the reasons in the [repository](https://git.sr.ht/~rhizoome/mond) of my terrible lua code. I also commited the qemu-9front script to the repo: examples/9front. ```lua function qemu:runner() local proc = Popen { "qemu-system-x86_64", "-enable-kvm", "-smp", "4", "-cpu", "host", "-m", "1024", "-device", "virtio-net-pci,mac=00:20:91:37:33:77,netdev=net0", "-netdev", "user,id=net0," .. "hostfwd=tcp:127.0.0.1:1564-:564," .. "hostfwd=tcp:127.0.0.1:4242-:4242", "-device", "virtio-scsi-pci,id=scsi", "-drive", "if=none,id=vd0,file=" .. img, "-device", "scsi-hd,drive=vd0", "-display", "none", "-monitor", "none", "-serial", "stdio", stdin = Popen.pipe, stdout = Popen.pipe } [...] repeat local new = self:check(proc.stdout) self:display(new) u.extend(self.buf, new) self:current() until self.done or proc.stdout:is_closed() end ``` We accumulate the output of 9front in `self.buf` until we find the expected output. ```lua function qemu:stage0() if u.tcat(self.buf):match("\nbootargs is") then self.qemin:write("\n") self.current = self.stage1 self.buf = { "\n" } end end ``` Then we write the next command, we move the state-machine to the next state (stage1) and we reset the accumulator `self.buf`. We do that till we have done the same we did manually. ## Moral of the Story We have learned that my brain likes nuts and bananas, as well as a wrong way of incrementally getting into 9front. Next I hope to fix some of the quirks 9front has on my hardware.