A client implementation of the 9P file protocol: https://man.cat-v.org/plan_9/5/intro

cage 3afd8f6bb4 - increased version number. 3 months ago
src c6921e64f2 - exported restart 'ignore-error'. 3 months ago
tests c17f8ba11c - removed leak of fid when reading directory. 5 months ago
COPYING 7f6d607ad6 - initial commit. 7 months ago
COPYING.LESSER 7f6d607ad6 - initial commit. 7 months ago
Changelog 0e80130881 - updated ChangeLog. 3 months ago
LICENSE 7f6d607ad6 - initial commit. 7 months ago
README.org a0a3525352 - increased version number; 3 months ago
README.txt a0a3525352 - increased version number; 3 months ago
purgatory-tests.asd 7f6d607ad6 - initial commit. 7 months ago
purgatory.asd 3afd8f6bb4 - increased version number. 3 months ago



This library aims to be an implementation of the 9p file protocol as described in:



The library is under development,most of the protocol has been implemented except for Twstat (https://man.cat-v.org/plan_9/5/stat); bugs, of course, exists, API may changes.


  • alexandria
  • cl-ppcre
  • usocket
  • babel
  • uiop
    To run the tests also
  • cl+ssl
  • clunit2

needs to be installed (via quicklisp).

Of course a 9p server is needed.


The best way to get purgatory working is using the excellent quicklisp

#+BEGIN_SRC common-lisp (ql:quickload "9p-client") #+END_SRC


A typical usage of the library should starts with a code similar to the one below:

#+BEGIN_SRC common-lisp (let ((messages-sent '()) (fid 1) (tag 8) (root-fid (mount stream root))) ...) #+END_SRC

Note that `stream` should be a stream of (unsigned-byte 8) and likely is created by a client socket. The file ./tests/all-tests.lisp contains a small macro with-open-ssl-stream that will help creating a TLS stream using usocket.

The file ./tests/protocol-tests.lisp contains a lot of examples about the usage of the library.

There are quite a few high level functions to use the filesystem that are listed below:

Initialize the connection to the server using `root-path' as the
root of the remote filesystem
  • read-all-pending-messages-ignoring-errors ::
  • Read all the messages from the server and ignore any error
    Make a new fid that points to the same resource pointed by `fid'
    Create a new directory with name `directory-name' under the
    directory pointed by `parent-fid'. The parameter `permissions' must be provided in octal (default: #o760))
  • path-exists-p
  • create-path
    Create a file or directory as specified by `path'.
    To create a directory specify a path terminating with a '/'. Parent directories are created if do not exist. The parameter `permissions' must be provided in octal (default: #o640))

    Returns the fid to the last element of `path'

    If the last element of `path' is a directory the directory is created with permissions: #o760.

    It `path' already reference a valid file it is opened for read and write.

    Finally if already reference a valid directory is opened for read only.
    Open a full, existing, path relative to `root-fid'. `Mode' must be
    one of ~+create-for-read+~ ~+create-for-write+~ ~+create-for-read-write+"~
    Read in a vector of (unsigned byte 8) the full content of
    'path' relative to `root-fid'
    Remove the last element of 'path' relative to `root-fid'.
    If `path' does not exists this function does nothing.
    Open the directory pointed to `path' relative to `root-fid'.
    Returns the fid of the directory pointed by `path'.

    This fid can be used by `read-directory` to get the directory entries (see the struct `stat')

    Read the next entry of a directory specified by `dir-fid'.

    `Dir-fid' is usually the values returned by `open-directory`.

    This function on success returns tree values:

    • dir-fid to read the next entry or to check if there are no more
    • entries (see below);
    • the stat struct for the current entry;
    • an offset to the next entry.

    Each call to this function on the same directory fid, except the first, must use the offset returned by the last call, for example

    #+BEGIN_SRC common-lisp (let* ((dir-fid (open-directory stream root-fid path))) (multiple-value-bind (next-dir-fid stat stat-size) (read-directory stream dir-fid) ; offset 0 on the first call (multiple-value-bind (next-dir-fid-2 stat-2 next-stat-size-2) (read-directory stream dir-fid stat-size) ; offset equals ; to stat-size ; from previous ; call ...))) #+END_SRC

    When this function returns nil there no more entries for this directory (see the code of directory children).
    Sort a list of stat struct `data' (by default sorting entry names
    in lexicographic ascending order
    Traverse the file system tree starting at
    `starting-directory' returning a list of strings representing the paths of all files under `starting-directory'.
    Traverse the file system tree starting at `starting-directory'
    returning two values
  • a list of strings representing the paths of all the files
  • under `starting-directory';
  • a list of all the path to the sub-directories under `starting-directory'.
  • The lists are ordered in topological order (depth first strategy).

    Copy a file pointed by path-from to a file pointed by path-to.
    ~progress-fn~, if defined, must be a function with a single parameter: the number of octects wrote in a single call of the ~Twrite~ command.

    Note: the destination file is overwritten if does exists."

    Also checking the protocol documentation is useful https://man.cat-v.org/plan_9/5/intro


    Please send bug reports or patches to the issue tracker.


    This library is released under Lisp Lesser General Public license (see COPYING.LESSER file)


    This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.


    My deep thanks to op for the support and redfog for the name of the library, Thank you!