Running u9fs on macOS

I use the following technique to run u9fs on macOS. I’m currently running this on macOS 14 (Sonoma), but I believe I’ve used the same technique on several prior versions.

My primary use of this is to serve a root file system to Plan 9 running under qemu. I have it perform real Plan 9 authentication against my auth server.

Creating needed files

The u9fs binary

You’ll need a u9fs binary, of course.

I use the tree from https://github.com/unofficial-mirror/u9fs. It should work as-is. There’s a good argument for just deleting authrhosts.c and removing it from authmethods in u9fs.c at this point, but I haven’t done that.

Put the resulting binary somewhere stable.

The launchd file

In /Library/LaunchDaemons I create a file 9pfs.plist containing the following:

<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> 
<dict> 
    <key>Label</key> 
    <string>org.p9f.u9fs</string> 
    <key>Program</key> 
    <string>/Users/a/bin/MacOSX/arm64/u9fs</string> 
    <key>ProgramArguments</key> 
    <array> 
        <string>u9fs</string> 
        <string>-l</string> 
        <string>/var/log/u9fs.log</string> 
        <string>-a</string> 
        <string>p9any</string> 
        <string>/opt/plan9</string>
    </array> 
    <key>Sockets</key> 
    <dict> 
        <key>Listeners</key> 
        <dict> 
            <key>SockServiceName</key> 
            <string>9pfs</string> 
        </dict> 
    </dict> 
    <key>inetdCompatibility</key> 
    <dict> 
        <key>Wait</key> 
        <false/> 
    </dict> 
</dict> 
</plist>

A few things you’ll want to adjust:

Permissions on this file matter to launchd. I’m not sure exactly what the criteria are, but mine is mode 644 and owned by root:wheel, and everything is happy.

The key file

If you’re doing real Plan 9 authentication, create a file /etc/u9fs.key. Note: this file will contain a sensitive key, so be cautious with permissions. Mine is mode 640 and owned by root:wheel. It should not be world-readable.

This file contains exactly three lines. They are, in order:

The conventional way to do this is to put your Plan 9 hostowner info in there, but I don’t like sticking that in additional places. I create a new user, unixroot, and use that. So my file looks like:

YourSuperSecretPassword
unixroot
9srv.net

That user must exist on the auth server; see auth/changeuser in auth(8). The user doesn’t need to exist on the mac.

Optional: symlink /usr/Users

If you’re using this to serve Plan 9 qemu images like I am, it’s very useful to create a symlink from usr in the root of your tree to your real /Users directory. This way your qemu images will get your real home directory.

Running the results

I’m not 100% clear on when launchd re-reads its config information, but I did the below, as root, to get it listening after setting things up like above.

:; cd /Library/LaunchDaemons
:; launchctl load org.p9f.u9fs
:; launchctl start org.p9f.u9fs

You should then see one instance returned if you run launchctl list | grep u9fs, plus one instance with the name followed by a UUID for each active connection.

This should get picked up on subsequent reboots without your intervention.