You are looking at historical revision 8720 of this page. It may differ significantly from its current revision.

9p

Description

A pure Scheme implementation of the 9p networked filesystem protocol.

Author

Peter Bex

Requirements

Requires the iset egg.

Download

9p.egg

Documentation

9p is an implementation of the networked file system protocol known as 9P, specifically version 9P2000 which is also known as "Styx". This protocol is used by the Plan 9 operating system and the wmii window manager, among others.

This implementation includes a low-level implementation of the protocol that is suitable both for writing clients and servers and a high-level client implementation. There currently are no concrete plans for a high-level server implementation, but contributions are of course very welcome :)

The low-level implementation is documented below under 9p-lolevel and the high-level client implementation under 9p-client. The high-level client is discussed first, because this is the one you will most likely need.

9p-client

The basic library was modeled after Chicken's Unit posix and a few choice other procedures that interact with the filesystem. Most procedures from Unit posix are available under the same name, prefixed with 9p:. Where possible, the procedure's signature has been unmodified, except for an additional leading argument that specifies the connection with the 9p server.

Usage

 (use 9p-client)
 (use utf8)

It is highly recommended you require utf8 in your applications, as 9p is a utf8-aware protocol. It is not a dependency of this egg because in some situations you might decide it's safe to leave it out, for performance or memory footprint reasons.

Connection management

9p:client-connect

Before doing anything else, you must establish a connection with the server. This is done with the 9p:client-connect procedure.

   procedure: (9p:client-connect inport outport [user] [mountpoint])

The inport and outport arguments are the ports you use to communicate to the server. The user argument is the name of the user that creates the files. It defaults to the empty string. There is no support for authentication, so the user name is simply used for newly created files on servers that support usernames (wmii doesn't, for example). The mountpoint also defaults to the empty string, which selects the "default mount point" on the server. If the server has multiple mountpoints it exports, you can select with this argument.

The procedure returns a connection object you must keep and use in all subsequent 9p procedure calls.

You can use the following procedures to obtain some more information on the connection:

 procedure: (9p:connection-outport connection)
 procedure: (9p:connection-inport connection)

Get back the underlying ports you passed to 9p:client-connect.

 procedure: (9p:connection-message-size connection)

The maximum size of a low-level message as negotiated in the connection handshake. Not very useful unless you would like to write some custom messages. This includes the size of the tag (2 bytes) and the message type (1 byte).

9p:client-disconnect
   procedure: (9p:client-disconnect connection)

Disconnect from the server described by connection. This clunks any fids that are still open (in Unix terms: closes any open file descriptors).

9p:connection?
  procedure: (9p:connection? object)

You can verify an object is a connection to a 9p server with this predicate.

Files as ports

9p:with-output-to-file
 procedure: (9p:with-output-to-file connection file thunk)

Open file on the 9p connection connection and call thunk with the current-output-port set to a port that writes to the file. When the thunk finishes, the port is closed.

9p:call-with-output-file
 procedure: (9p:call-with-output-file connection file procedure)

Open file on the 9p connection connection and call procedure with an output-port that corresponds to the file. When the procedure finishes, the port is closed. Procedure should accept one argument, the output-port.

9p:open-output-file
 procedure: (9p:open-output-file connection file [mode])

Create an output port that will write to the given file on the 9p connection connection. If the file exists, it is truncated. If it does not exist yet it will be created. If the optional mode is given, it determines with what permissions the file will be created, if it is a new file. See below for the list of file permissions.

Don't forget to close the output port (with close-output-port) when you finish writing to it!

9p:with-input-from-file
 procedure: (9p:with-input-from-file connection file thunk)

Open file on the 9p connection connection and call thunk with the current-input-port set to a port that reads from the file. When the thunk finishes, the port is closed.

9p:call-with-input-file
 procedure: (9p:call-with-input-file connection file procedure)

Open file on the 9p connection connection and call procedure with an input-port that corresponds to the file. When the procedure finishes, the port is closed. Procedure should accept one argument, the input-port.

9p:open-input-file
 procedure: (9p:open-input-file connection file)

Create an input port that will read from the given file on the 9p connection connection.

Don't forget to close the input port (with close-input-port when you finish reading from it!

Directories

9p:directory?
 procedure: (9p:directory? connection path)

Returns #t if the given path on the connection is a directory, #f if not.

9p:create-directory
 procedure: (9p:create-directory connection path permissions)

Create a directory on the connection with the given path. It will have the specified permissions, see below for the available permissions.

9p:directory
 procedure: (9p:directory connection directory [show-dotfiles?])

Returns a list with the contents of the directory on the connection. Files beginning with . are included only if show-dotfiles? is given and not #f.

Files

9p:regular-file?
 procedure: (9p:regular-file? connection path)

Returns #t if the given path on the connection is a regular file, #f if not. 9p does not support symlinks or FIFOs, so this is the same as (not (9p:directory? connection path)), even if the underlying FS is a Unix FS (the 9p egg currently does not (and probably will never) support 9P2000.u).

9p:delete-file
 procedure: (9p:delete-file connection path)

Delete the file indicated by path on the connection. If the file does not exist or you do not have permission to delete it, an error is signaled.

9p:file-stat
 procedure: (9p:file-stat connection path)

Returns a 9-element vector which contains information about the file indicated by path on the connection. It has the following contents:

9p:file-permissions
 procedure: (9p:file-permissions connection path)

Returns the permissions of the file indicated by path on the connection. See the permission bits section for a description of the possible bits.

9p:file-access-time
 procedure: (9p:file-access-time connection path)

Returns the access time of the file indicated by path on the connection. See the notes under 9p:file-stat.

9p:file-modification-time
 procedure: (9p:file-modification-time connection path)

Returns the modification time of the file indicated by path on the connection. See the notes under 9p:file-stat.

9p:file-size
 procedure: (9p:file-size connection path)

Returns the size, in bytes, of the file indicated by path on the connection.

9p:file-owner
 procedure: (9p:file-owner connection path)

Returns the name of the owner, as a string, of the file indicated by path on the connection.

9p:file-group
 procedure: (9p:file-group connection path)

Returns the name of the owning group, as a string, of the file indicated by path on the connection.

9p:file-last-modified-by
 procedure: (9p:file-last-modified-by connection path)

Returns the name of the user, as a string, who last changed the file indicated by path on the connection.

File handles and low-level calls

These calls are not on the protocol level, as the 9p-lolevel library procedures, but they are more low-level than the other procedures in the 9p-client library because they allow you to work on the file handle level.

9p:file-open
 procedure: (9p:file-open connection path mode)

Opens the file indicated by path on the connection with the given mode and returns an opaque handle object which you can use for the other procedures described in this section. For bit flags that the mode can take, see the open flags section.

9p:file-create
 procedure: (9p:file-create connection path permissions mode)

Creates and opens the file indicated by path on the connection with the given permission and mode and returns an opaque handle object which you can use for the other procedures described in this section. For bit flags that the mode can take, see the open flags section. For bit flags that the permission can take, see the permission bits section.

9p:file-close
 procedure: (9p:file-close handle)

Close the file indicated by handle. It is not an error to close a file more than once.

9p:file-read
 procedure: (9p:file-read handle size)

Read size bytes from the file with the given handle. This procedure returns a list with two values: the buffer containing the data and the number of bytes read.

9p:file-write
 procedure: (9p:file-write handle buffer [size])

Writes the contents of the string or bytevector buffer into the file with the given handle. If the optional argument size is given, then only the specified number of bytes are written.

9p:set-file-position!
 procedure: (9p:set-file-position! handle position [whence])

Sets the current read/write position of handle to position, which should be an exact integer. whence specifies how the position is to interpreted and should be one of the values seek/set, seek/cur and seek/end. It defaults to seek/set.

9p:file-position
 procedure: (9p:file-position handle)

Returns the current read/write position of the handle.

9p:handle-stat
 procedure: (9p:handle-stat handle)

Just like 9p:file-stat, except it works on a handle instead of on a connection with a filename.

Low-level handle access

If you want to get really dirty and low-level you can modify file handles with the following procedures. This is not recommended, but sometimes required if you want to do some custom things just above the protocol level and extend the client library instead of writing your own.

9p:path-walk
 procedure: (9p:path-walk connection path [starting-point])

Obtain a handle for the file identified by path on the connection without opening it. You must not forget to clunk the handle's FID (or just call 9p:file-close on the handle). starting-point is an optional handle to a directory from which to start walking. It defaults to the root directory (/).

9p:with-handle-to

If all you need is a temporary handle/FID for a message to the server, you can use this utility procedure:

 procedure: (9p:with-handle-to connection path procedure)

This will call procedure with one argument: a temporary handle which represents the path on the connection. After the procedure returns, the handle will be deallocated and the FID will no longer be valid. This returns whatever procedure returned. If a condition is signaled, the handle will be deallocated properly and the FID clunked.

9p:alloc-handle

The 9p-client library keeps track of FIDs for you so you do not have to remember numbers. If you wish to send low-level messages yourself you should allocate and release FIDs through the library so your FIDs can't clash with the FIDs the library uses:

 procedure: (9p:alloc-handle connection)

Allocate a handle on the connection. This returns a handle object which you can query with the following procedures:

 procedure: (9p:handle-connection handle)
 procedure: (9p:handle-fid handle)
 procedure: (9p:handle-position handle)
 procedure: (9p:handle-iounit handle)

The fid is allocated from an internal pool of free fids. The position is initialized to 0, and used as an offset for read/write procedures (the server does not keep track of this for us in the 9p protocol).

The iounit defaults to #f and you are expected to set it manually (normally, 9p:file-open and 9p:file-create do this for you). is returned as part of the Ropen and Rcreate replies and is the maximum size of a data transfer (either read or write). If the server returns 0, the iounit should default to the size returned by 9p:connection-message-size minus 24.

9p:release-handle

Once you are done with a handle, you must either pass the handle to 9p:file-close (or just disconnect with 9p:client-disconnect) or call 9p:release-handle:

 procedure: (9p:release-handle handle)

important: be sure to clunk the handle's fid first. 9p:release-handle does not clunk the fid.

Sending messages

A code using 9p-client normally never needs to send raw messages, but in case it does, there is one convenience procedure that does just a bit more than the raw 9p-lolevel procedures do:

 procedure: (9p:request connection type . args)

This creates a new 9p:message object (see below) with a tag and the given type. args are the message-contents. It then sends this request to the server and awaits a response. The response should match the request (a Twhatever should result in a Rwhatever message), or a condition of type (exn 9p-response-error) is signaled. If the server returns an error (via Rerror), a condition of type (exn 9p-server-error) is signaled. The response object (a 9p:message object) is returned.

9p-lolevel

This library allows you to build your own client or server abstraction library. This documentation will not make a lot of sense if you haven't read the 9p protocol documentation.

Usage

 (use 9p-lolevel)
 (use utf8)

Again, utf8 is highly recommended but not strictly required.

Messages

Messages are main concept in the 9p protocol. They can be created as follows:

 procedure: (make-9p:message type tag contents)

The type is a symbol, one of

 Tversion Rversion
 Tauth Rauth
 Tattach Rattach
 Rerror
 Tflush Rflush
 Twalk Rwalk
 Topen Ropen
 Tcreate Rcreate
 Tread Rread
 Twrite Rwrite
 Tclunk Rclunk
 Tremove Rremove
 Tstat Rstat
 Twstat Rwstat

As you can see, all messages (except Rerror) come in pairs: there is a transmit message (that starts with a T) and a response message (that starts with an R). The client sends transmit messages and the server sends response message in return. It must either send the matching response message or Rerror. It is not allowed to return a different message, nor is it allowed for the client to send a response message or the server to send a transmit message.

The tag is a unique identifier that allows the client to keep track of what it sent and what responses belong to what transmissions. The client sends a message with a given tag and the server will respond with the matching response message bearing the same tag. This allows a client to send messages asynchronously, as long as they all have a different tag. Then the responses can come in any order and be handled at any time and still be understood if the client keeps a list of sent tags and what transmissions belonged to them. The 9p-client library always sends messages synchronously, waiting for replies before sending new transmissions. This allows it to use a constant tag all the time.

The contents are a list whose contents differ per message type. For instance, a Tversion message's contents consist of an msize (a maximum message size) and a string which indicates the protocol version. Currently the 9p-lolevel implicitly assumes the 9P2000 version of the protocol because of the way it is constructed. If it turns out to be useful to support different versions, the egg's API will most likely change in order to allow for more flexibility.

You can of course query and modify the message objects with the following procedures:

 procedure: (9p:message? object)
 procedure: (9p:message-type message)
 procedure: (9p:message-type-set! message new-type)
 procedure: (9p:message-tag message)
 procedure: (9p:message-tag-set! message new-tag)
 procedure: (9p:message-contents message)
 procedure: (9p:message-contents-set! message new-contents)
9p:send-message
 procedure: (9p:send-message outport message)

Sends the message on the output-port outport.

9p:receive-message
 procedure: (9p:receive-message inport)

Waits for a message on input-port inport and returns a 9p message-object.

 

QIDs

A QID is an unique identifier for a file on the server; two QIDs are the same iff they point to the same file. A QID has three fields which can be queried with the following procedures:

 procedure: (9p:qid-type qid)
 procedure: (9p:qid-version qid)
 procedure: (9p:qid-path qid)

You can create a QID using the make-9p:qid procedure:

 procedure: (make-9p:qid type version path)

Finally, you can check if an object is a QID object with the 9p:qid? predicate:

 procedure: (9p:qid? object)

The fields of the QID will be described next.

First, the type of a QID is a bitwise field which consists of several of the following constants ORed together:

 9p:qtfile
 

9p:qtfile indicates that the file is, in fact, a file. Because everything in Plan9 is a file, this is always true, even for directories. It does not mean that the file is a regular file.

 
 9p:qtdir

9p:qtdir indicates that the file is a directory.

 9p:qtappend

9p:qtappend indicates that the file is an append-only file.

 9p:qtexcl

9p:qtexcl indicates that the file is marked for exclusive-use. This means that only one client can have this file open at any time.

 9p:qtauth

9p:qtauth indicates that the file is an authentication file established by AUTH messages.

 
 9p:qttmp

9p:qttmp indicates that the file is a "temporary file". In practice this means that the file is not included in nightly backups.

The version of a QID is a version number for the file which is incremented every time the file is modified.

The path of a QID is an integer that is unique among all files in the file hierarchy (ie, this uniquely identifies the file in the FS).

Permission bits

The permissions below can be ORed together bitwise to produce the desired permission mode. When creating new files, the execute bit is ignored by the server unless you're creating a directory, so it is safe to always include it.

Note: The 9p protocol documentation is not very consistent in naming these. Sometimes it refers to permissions as mode, and sometimes as perm or permission. On other occasions, it refers to the open flags as mode. Read carefully and check the context!

  9p:perm/irusr
  9p:perm/iwusr
  9p:perm/ixusr

These constants determine the permissions for the user who owns the file: read, write and execute, respectively.

  9p:perm/irgrp
  9p:perm/iwgrp
  9p:perm/ixgrp
  

These constants determine the permissions for the group that owns the file: read, write and execute, respectively.

  9p:perm/iroth
  9p:perm/iwoth
  9p:perm/ixoth

These constants determine the permissions for others: read, write and execute, respectively.

There are some additional "permissions" that can be used on Tcreate messages, which are not really permissions but rather modes that change the way the file behaves (hence the inconsistence of the docs). These are like the 'special' bits in Unix like sticky/setuid etc. These are the following:

 9p:dmdir

This is used to create directories instead of files with Tcreate.

 9p:dmappend

The file can only be appended to.

 9p:dmexcl

The file is 'exclusive', it can only be opened by one client at a time.

 9p:dmauth

The file is an authentication file, as established by AUTH messages.

 9p:dmtmp

The file is to be considered "temporary". In practice this means that it is not included in nightly backups.

Open flags

These flags are useful when opening a new file (for use in the Topen/Tcreate messages). These can be ORed together bitwise to produce the desired mode.

  9p:open/rdonly

The file is to be opened only for reading.

  9p:open/wronly

The file is to be opened only for writing.

  9p:open/rdwr

The file is to be opened both for reading and writing.

  9p:open/trunc

The file is to be truncated on opening.

  9p:open/rclose

The file is to be removed upon closing (ie, when the FID is clunked).

Utility procedures

9p:data->directory-listing
 procedure: (9p:data->directory-listing data show-dotfiles?)

Because the 9p protocol requires you to use the Tread/Rread messages to read both from files and directories, the Rread response can be considered to be a polymorphic type. In case of files, the data is simply a bytestream, but in case of directories, the data will be structured. This means the data needs to be decoded.

This procedures decodes the data obtained from the Rread message and returns a list of filenames which are the directory listing for the directory that was read. If show-dotfiles? is #f files starting with a dot are excluded from the list.

Note: The converse procedure, 9p:directory-listing->data, is currently not implemented.

Example

9p-client

Here's a simple example that talks to a wmii server.

(use 9p-client posix unix-sockets)

(receive (in out)
    (unix-connect (sprintf "/tmp/ns.~A.:0/wmii" (getenv "USER")))
  (let ((con (9p:client-connect in out)))
    (printf "Current tabs on left bar: ~A\n" (9p:directory con "/lbar"))
    (printf "Label on first tab on left bar: ~A\n"
            (9p:with-input-from-file con `("lbar" ,(car (9p:directory con "/lbar"))) read-string ))
    ;; Write something to the right bar
    (9p:with-output-to-file con "/rbar/status" (lambda () (printf "Yo, what's up?")))
    (9p:client-disconnect con)))

This prints something like

 Current tabs on left bar: (3 2 1)
 Label on first tab on left bar: #888888 #222222 #333333 3

And it shows the string "Yo, what's up?" on your status bar.

Changelog

License

 Copyright (c) 2008, Peter Bex
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
 met:
 
 Redistributions of source code must retain the above copyright
 notice, this list of conditions and the following disclaimer.
 
 Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in the
 documentation and/or other materials provided with the distribution.
 
 Neither the name of the author nor the names of its contributors may
 be used to endorse or promote products derived from this software
 without specific prior written permission.
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 OF THE POSSIBILITY OF SUCH DAMAGE.