STYXSERVERS(2)STYXSERVERS(2)NAMEstyxservers - 9P (Styx) server implementation assistance
SYNOPSIS
include "sys.m";
include "styx.m";
Tmsg, Rmsg: import Styx;
include "styxservers.m";
styxservers := load Styxservers Styxservers->PATH;
Styxserver, Fid, Navigator: import styxservers;
Styxserver: adt {
fd: ref Sys->FD; # file server end of connection
t: ref Navigator; # name space navigator for this server
msize: int; # negotiated 9P message size
new: fn(fd: ref Sys->FD, t: ref Navigator, rootpath: big)
:(chan of ref Tmsg, ref Styxserver);
reply: fn(srv: self ref Styxserver, m: ref Rmsg): int;
# protocol operations
attach: fn(srv: self ref Styxserver, m: ref Tmsg.Attach): ref Fid;
clunk: fn(srv: self ref Styxserver, m: ref Tmsg.Clunk): ref Fid;
walk: fn(srv: self ref Styxserver, m: ref Tmsg.Walk): ref Fid;
open: fn(srv: self ref Styxserver, m: ref Tmsg.Open): ref Fid;
read: fn(srv: self ref Styxserver, m: ref Tmsg.Read): ref Fid;
remove: fn(srv: self ref Styxserver, m: ref Tmsg.Remove): ref Fid;
stat: fn(srv: self ref Styxserver, m: ref Tmsg.Stat);
default: fn(srv: self ref Styxserver, gm: ref Tmsg);
replychan: chan of ref Rmsg; # replies sent here if not nil.
replydirect: fn(srv: self ref Styxserver, gm: ref Rmsg): int; # called by receiver for replychan
# check validity
cancreate: fn(srv: self ref Styxserver, m: ref Tmsg.Create)
:(ref Fid, int, ref Sys->Dir, string);
canopen: fn(srv: self ref Styxserver, m: ref Tmsg.Open)
:(ref Fid, int, ref Sys->Dir, string);
canread: fn(srv: self ref Styxserver, m: ref Tmsg.Read)
:(ref Fid, string);
canwrite: fn(srv: self ref Styxserver, m: ref Tmsg.Write)
:(ref Fid, string);
canremove: fn(srv: self ref Styxserver, m: ref Tmsg.Remove)
:(ref Fid, big, string);
# fid management
getfid: fn(srv: self ref Styxserver, fid: int): ref Fid;
newfid: fn(srv: self ref Styxserver, fid: int): ref Fid;
delfid: fn(srv: self ref Styxserver, c: ref Fid);
allfids: fn(srv: self ref Styxserver): list of ref Fid;
iounit: fn(srv: self ref Styxserver): int;
};
Fid: adt {
fid: int; # client's fid
path: big; # file's 64-bit unique path
qtype: int; # file's qid type (eg, Sys->QTDIR if directory)
isopen: int; # non-zero if file is open
mode: int; # if open, the open mode
uname: string; # user name from original attach
param: string; # attach aname from original attach
data: array of byte; # application data
clone: fn(f: self ref Fid, nf: ref Fid): ref Fid;
open: fn(f: self ref Fid, mode: int, qid: Sys->Qid);
walk: fn(f: self ref Fid, qid: Sys->Qid);
};
Navop: adt {
reply: chan of (ref Sys->Dir, string); # channel for reply
path: big; # file or directory path
pick {
Stat =>
Walk =>
name: string;
Readdir =>
offset: int; # index (origin 0) of first entry to return
count: int; # number of directory entries requested
}
};
Navigator: adt {
new: fn(c: chan of ref Navop): ref Navigator;
stat: fn(t: self ref Navigator, path: big): (ref Sys->Dir, string);
walk: fn(t: self ref Navigator, parent: big, name: string)
: (ref Sys->Dir, string);
readdir:fn(t: self ref Navigator, path: big,
offset, count: int): array of ref Sys->Dir;
};
init: fn(styx: Styx);
traceset: fn(on: int);
readbytes: fn(m: ref Styx->Tmsg.Read, d: array of byte):
ref Styx->Rmsg.Read;
readstr: fn(m: ref Styx->Tmsg.Read, s: string):
ref Styx->Rmsg.Read;
openok: fn(uname: string, omode,
perm: int, funame, fgname: string): int;
openmode: fn(o: int): int;
DESCRIPTION
When writing a file server, there are some commonly performed tasks
that are fiddly or tedious to implement each time. Styxservers pro‐
vides a framework to automate some of these routine tasks. In particu‐
lar, it helps manage the fid space, implements common default process‐
ing for protocol messages, and assists walking around the directory
hierarchy and reading of directories. Other tasks, such as defining the
structure of the name space, and reading and writing files in it, are
left to the file server program itself. Familiarity with Section 5 of
the manual which defines the protocol (see intro(5)), and with the rep‐
resentation of 9P messages in Limbo (see styx(2)), is a prerequisite
for use of this module.
Styxservers does not define or store any of the directory hierarchy
itself; instead it queries an external process for information when
necessary, through a value of type Navigator, which encapsulates commu‐
nication with that process. That process must be started up indepen‐
dently of each Styxserver; a channel to such a process should be pro‐
vided when starting a new Styxserver. The channel carries messages of
type Navop. Styxservers-nametree(2) provides a ready-made implementa‐
tion of such a process that is sufficient for many applications.
Styxserver keeps tabs on the fids that are currently in use, and remem‐
bers some associated information, such as the Qid path of the file,
whether it has been opened, etc. It does this using values of type
Fid.
Once the Styxservers module has been loaded, the init function must be
called before anything else, to initialise its internal state. The styx
argument should be an implementation of the styx(2) module, which will
be used to translate messages. Individual Styxserver instances do not
share state, and are therefore independently thread-safe.
Fid representation
Styxservers represents each active fid as a Fid value, which has the
following public members:
fid The integer fid value provided by the client to refer to an
active instance of a file in the file server, as described in
intro(5).
path The 64-bit qid path that uniquely identifies the file on the
file server, as described in intro(5). It is set by f.walk and
f.open (see below).
qtype The file's qid type; it is Sys->QTDIR if and only if the fid
refers to a directory. The value is set by f.walk and f.open
(see below).
isopen Non-zero if and only if the fid has been opened by an open(5)
message. It is initially zero, and set by f.open (see below).
mode Valid only if the fid has been opened. It has one of the values
Sys->OREAD, Sys->OWRITE, Sys->ORDWR, possibly ORed with
Sys->ORCLOSE, corresponding to the mode with which the file was
opened. It is set by f.open (see below).
uname The name of the user that created the fid.
param Set by Styxservers to the aname of the initial attach(5) mes‐
sage, and subsequently inherited by each new fid created by
walk(5), but not otherwise used by Styxservers itself, and may
be changed by the application.
data Unused by Styxservers; for application use. It might be used,
for instance, to implement a file that gives different data to
different clients.
f.clone(nf)
Copy the current state of all members of f except f.fid, into
nf, and return nf. Used by Styxserver.walk, and is needed by an
application only if it replaces that function.
f.walk(qid)
Make f refer to the file with the given qid: set f.path and
f.qtype from qid.path and qid.qtype. Used by Styxserver.walk
and is needed by an application only if it replaces that func‐
tion.
f.open(mode, qid)
Mark f as `open', set f.mode to mode, and set path and qtype to
the path and type of qid. Used by the implementations of open
and create messages. The default implementation of open(5) in
Styxserver obtains the value of mode from Styxserver.canopen
(below), and obtains the value of qid by querying the applica‐
tion's navigator.
Styxserver and file server state
Each Styxserver value holds the state for a single file server, includ‐
ing its active fids, the link to the external name space process, and
other internal data. Most of the state is manipulated through the mem‐
ber functions described below. The exceptions are two read-only val‐
ues: the Navigator reference srv.t which can be used to access that
navigator; and the file descriptor srv.fd that is the file server's end
of the connection to the 9P client. Both values are initially provided
by the file serving application, but can be accessed through the
Styxserver value for convenience. The file descriptor value is nor‐
mally used only through Styxserver.reply, but will be needed directly
if the caller needs the file descriptor value as a parameter to sys-
pctl(2) when insulating the serving process's file descriptors from the
surrounding environment.
The first set of functions in Styxserver provides common and default
actions:
Styxserver.new(fd, t, rootpath)
Create a new Styxserver. It returns a tuple, say (c, srv), and
spawns a new process, which uses styx(2) to read and parse 9P
messages read from fd, and send them down c; t should be a Navi‐
gator adt which the Styxserver can use to answer queries on the
name space (see ``Navigating file trees'', below). Rootpath
gives the Qid path of the root of the served name space.
srv.reply(m)
Send a reply (R-message) to a client. The various utility meth‐
ods, listed below, call this function to make their response.
When srv.replychan is not nil the function sends the R-message
to this channel. It is assumed that a process will drain replies
from it and call srv.replydirect when appropriate.
srv.attach(m)
Respond to an attach(5) message m, creating a new fid in the
process, and returning it. Returns nil if m.fid is a duplicate
of an existing fid. The value of the attach parameter m.aname
is copied into the new fid's param field, as is the attaching
user name, m.uname.
srv.clunk(m)
Respond to a clunk(5) message m, and return the old Fid. Note
that this does nothing about remove-on-close files; that should
be programmed explicitly if needed.
srv.walk(m)
Respond to a walk(5) message m, querying srv.t for information
on existing files.
srv.open(m)
Respond to an open(5) message m. This will allow a file to be
opened if its permissions allow the specified mode of access.
srv.read(m)
Respond to a read(5) message m. If a directory is being read,
the appropriate reply is made; for files, an error is given.
srv.remove(m)
Respond to a remove(5) message m with an error, clunking the fid
as it does so, and returning the old Fid.
srv.stat(m)
Respond to a stat(5) message m.
srv.default(gm)
Respond to an arbitrary T-message, gm, as appropriate (eg, by
calling srv.walk for a walk(5) message). It responds appropri‐
ately to version(5), and replies to Tauth (see attach(5)) stat‐
ing that authentication is not required. Other messages without
an associated Styxserver function are generally responded to
with a ``permission denied'' error.
All the functions above check the validity of the fids, modes, counts
and offsets in the messages, and automatically reply to the client with
a suitable error(5) message on error.
The following further Styxserver operations are useful in applications
that override all or part of the default handling (in particular, to
process read and write requests):
srv.canopen(m)
Check whether it is legal to open a file as requested by message
m: the fid is valid but not already open, the corresponding file
exists and its permissions allow access in the requested mode,
and if Sys->ORCLOSE is requested, the parent directory is
writable (to allow the file to be removed when closed). Canopen
returns a tuple, say (f, mode, d, err ). If the open request
was invalid, f will be nil, and the string err will diagnose the
error (for return to the client in an Rmsg.Error message). If
the request was valid: f contains the Fid representing the file
to be opened; mode is the access mode derived from m.mode,
Sys->OREAD, Sys->OWRITE, Sys->ORDWR, ORed with Sys->ORCLOSE; d
is a Dir value giving the file's attributes, obtained from the
navigator; and err is nil. Once the application has done what
it must to open the file, it must call f.open to mark it open.
srv.cancreate(m)
Checks whether the creation of the file requested by message m
is legal: the fid is valid but not open, refers to a directory,
the permissions returned by srv.t.stat show that directory is
writable by the requesting user, the name does not already exist
in that directory, and the mode with which the new file would be
opened is valid. Cancreate returns a tuple, say
(f, mode, d, err ). If the creation request was invalid, f will
be nil, and the string err will diagnose the error, for use in
an error reply to the client. If the request was valid: f con‐
tains the Fid representing the parent directory; mode is the
open mode as defined for canopen above; d is a Dir value con‐
taining some initial attributes for the new file or directory;
and err is nil. The initial attributes set in d are: d.name
(the name of the file to be created); d.uid and d.muid (the user
that did the initial attach); d.gid, d.dtype, d.dev (taken from
the parent directory's attributes); and d.mode holds the file
mode that should be attributed to the new file (taking into
account the parent mode, as described in open(5)). The caller
must supply d.qid once the file has successfully been created,
and d.atime and d.mtime; it must also call f.open to mark f open
and set its path to the file's path. If the file cannot be cre‐
ated successfully, the application should reply with an error(5)
message and leave f untouched. The Fid f will then continue to
refer to the original directory, and remain unopened.
srv.canread(m)
Checks whether read(5) message m refers to a valid fid that has
been opened for reading, and that the count and file offset are
non-negative. Canread returns a tuple, say (f, err); if the
attempted access is illegal, f will be nil, and err contains a
description of the error, otherwise f contains the Fid corre‐
sponding to the file in question. It is typically called by an
application's implementation of Tmsg.Read to obtain the Fid cor‐
responding to the fid in the message, and check the access.
srv.canwrite(m)
Checks whether message m refers to a valid fid that has been
opened for writing, and that the file offset is non-negative.
Canwrite returns a tuple (f, err); if the attempted access is
illegal, f will be nil, and err contains a description of the
error, otherwise f contains the Fid corresponding to the file in
question. It is typically called by an application's implemen‐
tation of Tmsg.Write to obtain the Fid corresponding to the fid
in the message, and check the access.
srv.canremove(m)
Checks whether the removal of the file requested by message m is
legal: the fid is valid, it is not a root directory, and there
is write permission in the parent directory. Canremove returns
a tuple (f, path, err); if the attempted access is illegal, f
will be nil and err contains a description of the error; other‐
wise f contains the Fid for the file to be removed, and path is
the Qid.path for the parent directory.The caller should remove
the file, and in every case must call srv.delfid before reply‐
ing, because the protocol's remove operation always clunks the
fid.
srv.iounit()
Return an appropriate value for use as the iounit element in
Rmsg.Open and Rmsg.Create replies, as defined in open(5), based
on the message size negotiated by the initial version(5) mes‐
sage.
The remaining functions are normally used only by servers that need to
override default actions. They maintain and access the mapping between
a client's fid values presented in Tmsg messages and the Fid values
that represent the corresponding files internally.
srv.newfid(fid)
Create a new Fid associated with number fid and return it.
Return nil if the fid is already in use (implies a client error
if the server correctly clunks fids).
srv.getfid(fid)
Get the Fid data associated with numeric id fid; return nil if
there is none such (a malicious or erroneous client can cause
this).
srv.delfid(fid)
Delete fid from the table of fids in the Styxserver. (There is
no error return.)
srv.allfids()
Return a list of all current fids (ie, the files currently
active on the client).
Newfid is required when processing auth(5), attach(5) and walk(5) mes‐
sages to create new fids. Delfid is used to clunk fids when processing
clunk(5), remove(5), and in a failed walk(5) when it specified a new
fid. All other messages should refer only to already existing fids,
and the associated Fid data is fetched by getfid.
Navigating file trees
When a Styxserver instance needs to know about the namespace, it
queries an external process through a channel by sending a Navop
request; each such request carries with it a reply channel through
which the reply should be made. The reply tuple has a reference to a
Sys->Dir value that is non-nil on success, and a diagnostic string that
is non-nil on error.
Files in the tree are referred to by their Qid path. The requests are:
Stat
Find a file in the hierarchy by its path, and reply with the
corresponding Dir data if found (or a diagnostic on error).
Walk
Look for file name in the directory with the given path.
Readdir
Get information on selected files in the directory with the
given path. In this case, the reply channel is used to send a
sequence of values, one for each entry in the directory, finish‐
ing with a tuple value (nil,nil). The entries to return are
those selected by an offset that is the index (origin 0) of the
first directory entry to return, and a count of a number of
entries to return starting with that index. Note that both val‐
ues are expressed in units of directory entries, not as byte
counts.
Styxserver provides a Navigator adt to enable convenient access to this
functionality; calls into the Navigator adt are bundled up into
requests on the channel, and the reply returned. The functions pro‐
vided are:
Navigator.new(c)
Create a new Navigator, sending requests down c.
t.stat(path)
Find the file with the given path. Return a tuple (d, err),
where d holds directory information for the file if found;
otherwise err contains an error message.
t.walk(parent, name)
Find the file with name name inside parent directory parent.
Return a tuple as for stat.
t.readdir(path, offset, count)
Return directory data read from directory path, starting at
entry offset for count entries.
Other functions
The following functions provide some commonly used functionality:
readbytes(m, d)
Assuming that the file in question contains data d, readbytes
returns an appropriate reply to read(5) message m, taking
account of m.offset and m.count when extracting data from d.
readstr(m, s)
Assuming that the file in question contains string s, readstr
returns an appropriate reply to read(5) message m, taking
account of m.offset and m.count when extracting data from the
UTF-8 representation of s.
openok(uname, omode, perm, fuid, fgid)
Does standard permission checking, assuming user uname is
trying to open a file with access mode omode, where the file
is owned by fuid, has group fgid, and permissions perm.
Returns true (non-zero) if permission would be granted, and
false (zero) otherwise.
openmode(o)
Checks to see whether the open mode o is well-formed; if it
is not, openmode returns -1; if it is, it returns the mode
with OTRUNC and ORCLOSE flags removed.
traceset(on)
If on is true (non-zero), will trace 9P requests and replies,
on standard error. This option must be set before creating a
Styxserver, to ensure that it preserves its standard error
descriptor.
Constants
Styxservers defines a number of constants applicable to the writing of
9P servers, including:
Einuse, Ebadfid, Eopen, Enotfound, Enotdir, Eperm, Ebadarg, Eexists
These provide standard strings for commonly used error condi‐
tions, to be used in Rmsg.Error replies.
Authentication
If authentication is required beyond that provided at the link level
(for instance by security-auth(2)), the server application must handle
Tauth itself, remember the value of afid in that message, and generate
an Rauth reply with a suitable Qid referring to a file with Qid.qtype
of QTAUTH. Following successful authentication by read and write on
that file, it must associate that status with the afid. Then, on a
subsequent Tattach message, before calling srv .attach it must check
that the Tattach's afid value corresponds to one previously authenti‐
cated, and reply with an appropriate error if not.
SOURCE
/appl/lib/styxservers.b
SEE ALSOstyxservers-nametree(2), sys-stat(2), intro(5)STYXSERVERS(2)