Notes on Plan9's 9P protocol

The 9P protocol is the protocol for network file systems used in Plan9. Plan9 is not a widely used operating system, but it’s widely considered more true to the Unix spirit than Unix is, coming from some of the same people who made Unix as well.


To get a rough idea of the 9P protocol, consider a system where all the original core Unix syscalls (open, close, read, write, …) are lifted to be remote procedure calls. These can be sent over the network, making file systems in Plan9 network transparent.

9P’s RPCs are documented in Plan9’s man page section 5 (mirror). The supported methods are:

In addition, there is an error response which may be returned as response to any of these requests.

The handle to a file is called “file ID” (fid) in Plan9 lingo. This can be thought of as a file descriptor.

The 9P protocol does away with a bunch of complexities that Unix, and particularly Linux has started to have. Some of the non-supported features are:

Playing with Plan9port

An easy way to play with 9P is to install Plan9port, a Linux userspace port of Plan9’s core tools and wiring, which comes as installable package with many Linux distributions.

Plan9port includes the Plan9 compiler, the acme text editor and other things, including source code. Many of these tools are exposing 9P file systems over Unix sockets in the /tmp/ns.$USER.$DISPLAY directory. You can get your exact location by running the namespace tool:

$ namespace

For example, you might want to start up the factotum authentication service and have a peek at the created file system. Plan9port’s 9p tool lets you do simple accesses from the command line:

$ /usr/lib/plan9/bin/factotum
$ ls $(namespace)
$ 9p ls factotum

There are also public 9P services, such as the Plan9 “sources” repository. After the Bell Labs server went down, these continue to be mirrored by services such as The 9fs script is a wrapper for quickly mounting this remote service, but needs a little modification for convenience:

$ cat $PLAN9/bin/9fs \
  | sed -e 's/' > ~/9fs
$ chmod +x ~/9fs
$ ~/9fs sources
$ 9p ls -l sources
d-rwxrwxr-x M 0 9grid  9grid       0 Jul 15  2018 9grid
--rw-rw-r-- M 0 bootes sys         0 Jun 24  2013 _sources
d-rwxrwxr-x M 0 adm    sys         0 Jul 15  2018 adm
d-rwxrwxr-x M 0 sys    sys         0 Oct 28 19:18 contrib
d-rwxrwxr-x M 0 bootes sys         0 Jul 15  2018 dist
d-rwxrwxr-x M 0 sys    sys         0 Jul 15  2018 extra
--rw-rw-r-- M 0 bootes adm   9142602 Jan 23  2015 lsr
d-rwxrwxr-x M 0 geoff  nix         0 Jul 15  2018 nix
d-rwxrwxrwx M 0 glenda sys         0 Jul 15  2018 patch
d-rwxrwxr-x M 0 sys    sys         0 Jul 15  2018 plan9
d-rwxrwxr-x M 0 sys    sys         0 Jul 15  2018 wiki
d-rwxrwxr-x M 0 xen    xen         0 Jul 15  2018 xen

Hint: To make browsing more convenient, you may also use the FUSE file system which comes with Plan9port:

$ 9pfuse unix\!/tmp/ns.$USER.:0/sources /tmp/sources
$ ls /tmp/sources
9grid  contrib  extra  nix    plan9     wiki
adm    dist     lsr    patch  _sources  xen

Looking under the hood

Let’s first read a small file from the Plan9 sources repo:

$ 9p read sources/plan9/NOTICE
Copyright © 2002 Lucent Technologies Inc.
All Rights Reserved

To look under the hood of 9P, we can use the 9p tool’s -D option to print all requests and responses.

$ 9p -D read sources/plan9/NOTICE
<- Tversion tag 0 msize 8192 version '9P2000'
-> Rversion tag 65535 msize 8192 version '9P2000'
<- Tauth tag 0 afid 0 uname me aname <nil>
-> Rerror tag 0 ename authentication rejected
<- Tattach tag 0 fid 0 afid -1 uname me aname
-> Rattach tag 0 qid (0000000000000002 0 d)
<- Twalk tag 0 fid 0 newfid 1 nwname 2 0:plan9 1:NOTICE
-> Rwalk tag 0 nwqid 2 0:(0000000000081aa0 78 d) 1:(0000000000081d2d 1 )
<- Topen tag 0 fid 1 mode 0
-> Ropen tag 0 qid (0000000000081d2d 1 ) iounit 8168
<- Tread tag 0 fid 1 offset 0 count 4096
-> Rread tag 0 count 63 '436f7079 72696768 7420c2a9 20323030 32204c75 ...'
Copyright © 2002 Lucent Technologies Inc.
All Rights Reserved
<- Tread tag 0 fid 1 offset 63 count 4096
-> Rread tag 0 count 0 ''
<- Tclunk tag 0 fid 1
-> Rclunk tag 0

The 9p tool opens the Unix socket at /tmp/ to talk to the backend service.

The steps are:

  1. Version negotiation: Both sides confirm the 9P variant they want to speak.
  2. Authentication: The auth call is normally supposed to return an open file ID, over which to speak an authentication protocol. In this case, the service permits unauthenticated read accesses, so this is returning an error.
  3. Attaching to the service, retrieving the desired root directory, in this case it’s just the root. (Note, the client picks which FID it would like to use for identifying files.)
  4. Walking down to the desired file from the root FID 0, yielding the new FID 1.
  5. Opening the FID for reading.
  6. Calling read until nothing is returned any more.
  7. Closing the FID with clunk.

Some resources