muir↗CADR PRESERVED.
QUUX EVOLVED.

OZD · THE ASSOCIATED MACHINE FOR A SITE OF MIT CADR LISP MACHINES

ozd

ozd gives a site’s Lisp Machines their files, the time and their host table.

ozd is for the CADR: it serves its releases. QUUX does not use it, having a file device and a real-time clock of its own.

ozd is written in Rust and has no crate dependencies. It runs on Linux and macOS.

01 / WHAT IT IS

The host a band
asks for its files.

When a CADR boots — in muir-sim, or on an FPGA board with muir-fpga — it asks another host for the date and for its files. MIT called that host its associated machine.

At MIT that was OZ. ozd is OZ as a daemon: one host on one Chaosnet subnet, reached over UDP, for one machine or many.

What a band prints once it has booted, from sys/io/disk.lisp:

MIT Lisp Machine One, with associated machine OZ.

OZ, also called MIT-OZ, was the host where MIT’s Lisp Machines kept their system files. They also asked it for the time and for their host table. Several machines can share one ozd, just as MIT had one associated machine for many Lisp Machines.

02 / SEVEN SERVICES

Seven services.

Four because a machine does not finish booting without them, one because a cold load does not, and two because a band calls them at its associated machine. A simple transaction is one request and one answer; a stream is a connection.

ContactKindWhat ozd answersWhen a band asks
STATUSsimple transactionIts name, and the counters of its subnet(hostat), which asks every host
TIMEsimple transactionThe universal time, from the system clockWhen it wants the date, from the site’s time servers
UPTIMEsimple transactionHow long ozd has been up(uptime)
FILEstreamThe files in its roots, to read, write, rename and deleteFor its sources and its users’ files, from the file host its site names
MINIstreamThe same files, read only, one after another(si:qld), in a cold load that has no FILE yet
HOSTABstreamA host’s names, address and system typeWhen it meets a host name that its own table lacks
NAMEstreamThat nobody is logged in(finger) with no host

1/60 s

The unit of UPTIME’s answer, as the band’s own server sends it and its own user end divides it. The manual says seconds. The band’s code says sixtieths, and the band is what reads the answer.

Cold-loaded, a band has a reader, an evaluator and a fasloader, and no FILE yet. (si:qld) reads the rest of it through MINI: one file at a time, on one connection that is never closed. Each file goes whole, and then an EOF.

A cold load sends to the address that its cold/mini.lisp was compiled with. System 100’s names 3060, OZ, so an ozd at 3060 serves it as it is. System 1000 and later take the host that SYS: translated to when it was compiled.

What goes over a MINI connection, as MIT’s own server, cold/minisr.mid, lists it:

OpcodeFromWhat it carries
200 201the machineAn open, for characters or for binary, with the file name
202ozdThe truename, a newline and the file’s date
203ozdWhy the file cannot be read, and a newline
200 300ozdThe file, as characters or as 16-bit words
EOFozdThe end of the file; the next open may follow

MINI finds a file as FILE does when it reads one, so nothing outside the roots is read.

03 / THE SWITCH

Then one cable.
Now a link each.

THEN: ONE CABLE

one cable: every node hears every packet CADR 3050 CADR 3051 CADR 3052 OZ 3060

Chaosnet ran on one coaxial cable, which AIM-628 calls the ether. A packet one node sends arrives at every other node, and each decides in hardware whether to keep it, much as on the coaxial Ethernet of its day.

The memo explains whose turn it is to send by analogy with a ring’s token, but the cable itself is a line: its ends may not be joined into a circle.

NOW: A LINK EACH

No cable is shared any more. Ethernet gives each host a link of its own to a switch, and UDP goes point to point. So ozd is the switch.

Each machine sends its packets to ozd. ozd passes each one on to the host it is addressed to, or to every host but its sender if it is a broadcast, and changes nothing on the way.

FIG. 02 — OZD, THE SWITCH OF ONE SUBNETOZD
Lisp Machine 3050, MIT-LISPM-1 Lisp Machine 3051 more machines 3052, 3053, ... CHUDP over UDP port 42042 ozd 3060, MIT-OZ the switch of one subnet STATUS TIME UPTIME FILE MINI HOSTAB NAME FILE MINI base root, writable the users' homes, served as / --root /srv/lispm a mount, read-only the release's sources, at /tree --root tree=...,ro TCP a TCP client, such as nc carried to TELNET at a machine --tcp 10000,TELNET@3050
Every machine names ozd as its default CHUDP peer. ozd learns each host’s endpoint from the packets it sends, as an Ethernet switch learns where each host is, and --peer fixes one that must not move. FILE and MINI serve one tree: a base root, and roots mounted at its top level, each read-only or not. A --tcp listener carries a TCP client to a machine’s stream through the same switch.

What ozd does with a packet, by where it is addressed:

Addressed toozd
3060, this hostanswers it
0, a broadcastanswers it, and passes it to every endpoint but its sender’s
3051, another machinepasses it on, byte for byte
3052, a host not yet heard fromdrops it, having nowhere to send it
4401, another subnetdrops it: ozd routes nothing

Unlike an Ethernet switch, ozd drops a packet for a host it has not heard from yet, rather than sending it to every port.

It is a switch and not a bridge: no forwarding count, no routing, and nothing to another subnet.

04 / BUILD AND RUN

Build it.
Run a site.

ozd is built from its source with Cargo. Its compiler is pinned in rust-toolchain.toml, and nothing else is downloaded.

01

The Rust toolchain.

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Install rustup, and open a new shell. A Cargo from your system’s packages ignores rust-toolchain.toml and may be too old for the 2024 edition.

02

Clone and build.

git clone https://github.com/metebalci/ozd
cd ozd
cargo build --release

rustup downloads the pinned toolchain the first time Cargo runs here. ozd has no crate dependencies.

03

Run a site for MIT’s system.

target/release/ozd --address 3060 --name MIT-OZ,OZ,system=UNIX \
    --root /srv/lispm --root tree=/path/to/system-100-0/sys,ro \
    --host 3050,MIT-LISPM-1,CADR-1,CADR1,LM1,system=LISPM \
    --file-dates mit --timezone 5

The host table of MIT’s released system lists the Lisp Machine as MIT-LISPM-1 at 3050 and its file and time host as MIT-OZ at 3060, so ozd takes that address and those names. Every root must be an existing directory when ozd starts.

--address, --name
This host as the band’s host table has it: its address, its names with the official one first, and its system type.
--root /srv/lispm
The base root. It holds the users’ home directories, and ozd writes to it. Each user’s is /<user>/ in lower case: create it and make it writable, or a band that compiles stops with "Access to file denied".
--root tree=...,ro
The release’s sources, mounted read-only at /tree, which is where the band looks for them.
--host, --hosts-text
Machines for HOSTAB to answer for, one flag each, or a band’s own hosts.text, so that a band whose table lacks a machine can still find it by name.
--listen
Where ozd answers. Without it, 127.0.0.1:42042, which only this host can reach.
--peer <addr>@<ip>
The fixed endpoint of a host that must not move. Every other endpoint is learned.
--tcp 10000,TELNET@3050
A TCP port that ozd carries to a Chaosnet stream at a machine of its subnet. Nothing listens without it (Into the machine).
--file-dates mit|utc
How FILE writes and reads a file’s dates. The default, utc, is plain UTC with no daylight saving, as System 1002 and later put dates on the wire. Systems 100 to 1001 write them in their site’s zone with MIT’s daylight saving on top, and want --file-dates mit with their --timezone.
--timezone 5
With --file-dates mit, the band’s zone, as its site’s :TIMEZONE: whole hours west of Greenwich, 5 for System 100’s site. FILE’s dates are printed and read at it, with the band’s own daylight saving on top, the old North American calendar, whatever the zone. Without --file-dates mit it is refused.
--log-simple, --log-file, --log-file-probe, --log-mini, --log-tcp
More lines in the log: each simple transaction answered; each file read, directory listed and login; each FILE probe; each MINI open; each TCP connection opened and closed.

The same flags, one per line, can go in a file of flags that -c names, or OZD_RC, or .ozdrc. --check reads the flags, checks the roots and exits, and --help describes each flag.

04

Ask it.

cargo run --example ask 3060@127.0.0.1:42042
asking 3060 at 127.0.0.1:42042, from 3376
STATUS  MIT-OZ
TIME    2026-09-13T13:24:58Z (+0 s from this host)
UPTIME  0d 1h 6m 45s
FILE    / holds 2 directories and 1 file

It asks for STATUS, TIME and UPTIME over CHUDP, as a Lisp Machine would, then lists a directory through FILE and counts what is in it.

05

Point the machines at it.

A machine names ozd as its default CHUDP peer: its address, then where it listens, 3060@127.0.0.1:42042 on the same computer. muir-sim, a simulator of the CADR, and muir-fpga, the CADR on an FPGA board, both work with ozd this way.

On one computer each program needs its own UDP port: ozd takes 42042, a machine 42043, the next 42044. On separate computers start ozd with --listen 192.0.2.10, and each machine names 3060@192.0.2.10.

A machine whose CHUDP sends a unicast packet only to a peer named for its destination also names every other machine’s address at ozd’s endpoint, so that its packets for them pass through the switch.

05 / INTO THE MACHINE

Into the machine.

A --tcp listener takes TCP connections on the Unix side and carries each one to a Chaosnet stream at a machine. TELNET comes first.

A band serves TELNET. What it lacks is a Unix end to reach it from. Every machine already names ozd as its peer, so ozd listens on a TCP port and carries what comes in to the machine’s TELNET.

01

Listen.

target/release/ozd ... --tcp 10000,TELNET@3050

A bare port binds the loopback and nothing else. Each connection ozd takes there becomes a stream from ozd to TELNET at 3050, with the bytes going both ways. The machine must be on ozd’s subnet, and ozd must have heard from it.

02

Connect.

nc 127.0.0.1 10000

This reaches the Lisp top level of the machine at 3050. When either end closes, the other is closed too.

Nothing here is TELNET’s in particular: --tcp <endpoint>,<CONTACT>@<addr> names any stream contact, one for each listener, and may be given more than once. --log-tcp writes a line when a connection opens and one when it closes.

A TELNET connection is a Lisp top level with no login, for whoever reaches the port.

So nothing listens without --tcp, a bare port binds only the loopback, and each listener names its one contact, so that SUPDUP or EVAL is never reachable by accident. Name another address only where every host that can reach it may use the machine.

06 / WHO CAN REACH IT

Who can reach it.

ozd does not ask who you are. Any host that reaches its socket gets an answer, and FILE too.

ozd is meant for a trusted network segment. LOGIN accepts any name and only records it; it is never a credential. So the address ozd listens on decides who can use it: without --listen only this host can, and with 0.0.0.0 every host that can reach it can. ozd has no TLS and does no routing.

WHAT OZD HOLDS TO

A pathname cannot climb out of its root: .. and . are refused, not normalised. A symbolic link is followed only while it stays inside its own root; a command that asks for a link itself, as DELETE, RENAME and an open with INHIBIT-LINKS do, acts on the link and never on what it leads to; and a directory from elsewhere is mounted with --root <name>=<path>, never linked in.

Anything that is not a regular file or a directory is refused, and a ,ro root refuses every write before the disk is touched. ozd refuses to run as root, and needs no privilege: 42042 is an ordinary UDP port. It opens a TCP port only for a --tcp, and only on the loopback unless an address is named.

WHAT THE OPERATOR HOLDS TO

  1. The roots belong to ozd’s own user, and nobody else writes to them. A hard link inside a root to a file outside it looks like an ordinary file, and so does a bind mount. If only ozd’s user can write to a root, neither can appear there.
  2. One ozd per root, and each ,ro root read-only on disk as well. ozd checks a path and then opens it, which is safe only because nothing else writes to a root in between.

07 / AS A SERVICE

As a service.

contrib/ has a systemd unit and a launchd daemon. Both run ozd as a user of its own, which owns the writable roots and nothing else. The systemd unit is in use; the launchd daemon has not been tried yet.

Linux, with systemd.

sudo contrib/install-systemd.sh

Build ozd first, and run the script from the source directory. It creates the ozd user and the base root /srv/lispm, installs the binary and the unit, writes /etc/ozdrc unless one exists, checks the flags and the roots as that user, and starts the service. With --dry-run it prints what it would do and changes nothing, which is all it has been checked with.

Edit /etc/ozdrc to suit your site, then sudo systemctl restart ozd. The log goes to the journal: journalctl -u ozd.

macOS, with launchd.

sudo cp contrib/com.metebalci.ozd.plist /Library/LaunchDaemons/

Create a hidden system user named _ozd that owns the writable roots, for example with dscl. Install the binary as /usr/local/bin/ozd and the file of flags as /usr/local/etc/ozdrc, and copy the plist into place.

08 / ABOUT

Read from
the sources.

Every fact about a protocol is read from the Lisp Machine’s own manual and sources: AIM-628, sys/man/chaos.text, sys/doc/chfile.text and the machine’s network and cold-load code. docs/protocols.md says where each one was read.

The name is OZ daemon. OZ was the host that did the same job for the Lisp Machines at the MIT AI Lab in the 1980s.

ozd is written in Rust with Claude Code, using Anthropic’s Claude Opus, and has no crate dependencies.

© 2026 Mete Balci. ozd is AGPL-3.0-or-later.