Plan 9.r post 01/B46A - 9front, qemu, lua
DISCLAIMER: If you want to know what I discover
inside the box keep on reading. If you want to know how to use the box
properly please go to 9front.org. For
plan9 experts reading this: I promise to do everything by the book one
day and as much as I can as a go, but that is not my priority.
This
is about my learning process: Everything I post worked at the time, but
has probably been replace by something better or more correct by
now.
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 in the last post.
Prerequisites
We download 9front-*.amd64.qcow2.gz from 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:
» 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.
» 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
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.
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
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.
» 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
» 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.
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 of my terrible lua code. I also commited the qemu-9front script to the repo: examples/9front.
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()
endWe accumulate the output of 9front in self.buf until we
find the expected output.
function qemu:stage0()
if u.tcat(self.buf):match("\nbootargs is") then
self.qemin:write("\n")
self.current = self.stage1
self.buf = { "\n" }
end
endThen 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.
I just invented that name.↩︎