code: 9ferno

ref: 6bb619c8db2867ddd9cd19c0aec05065f5ee0cae
dir: /man/2/styxservers/

View raw version
.TH STYXSERVERS 2
.SH NAME
styxservers \-
9P (Styx) server implementation assistance
.SH SYNOPSIS
.EX
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;
.EE
.SH DESCRIPTION
When writing a file server, there are some
commonly performed tasks that are
fiddly or tedious to implement each time.
.B Styxservers
provides a framework to automate some of these
routine tasks.
In particular, it helps manage the fid space,
implements common default processing 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
.IR intro (5)),
and with the representation of 9P messages in Limbo
(see
.IR styx (2)),
is a prerequisite for use of this module.
.PP
.B 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
.BR Navigator ,
which encapsulates communication with that process.
That process must be started up
independently of each
.BR Styxserver ;
a channel to such a process should be provided
when starting a new
.BR Styxserver .
The channel carries messages of type
.BR Navop .
.IR Styxservers-nametree (2)
provides a ready-made
implementation of such a process that is sufficient for many applications.
.PP
.B Styxserver
keeps tabs on the fids that are currently in use, and remembers
some associated information, such as the Qid path
of the file, whether it has been opened, etc.
It does this using values of type
.BR Fid .
.PP
Once the
.B Styxservers
module has been loaded,
the
.B init
function must be called before anything else,
to initialise its internal state. The
.I styx
argument should be an implementation of
the
.IR styx (2)
module, which will be used to translate messages.
Individual
.B Styxserver
instances do not share state, and are therefore
independently thread-safe.
.SS Fid representation
.B Styxservers
represents each active fid as a
.B Fid
value,
which has the following public members:
.TF param
.TP
.B fid
The integer
.I fid
value provided by the client to refer to an active instance of a file in the file server,
as described in
.IR intro (5).
.TP
.B path
The 64-bit qid path that uniquely identifies the file on the file server,
as described in
.IR intro (5).
It is set by
.IB f .walk
and
.IB f .open
(see below).
.TP
.B qtype
The file's qid type; it is
.B Sys->QTDIR
if and only if the fid refers to a directory.
The value is set by
.IB f .walk
and
.IB f .open
(see below).
.TP
.B isopen
Non-zero if and only if the fid has been opened by an
.IR open (5)
message.
It is initially zero, and set by
.IB f .open
(see below).
.TP
.B mode
Valid only if the fid has been opened.
It has one of the values
.BR Sys->OREAD ,
.BR Sys->OWRITE ,
.BR Sys->ORDWR ,
possibly ORed with
.BR Sys->ORCLOSE ,
corresponding to the mode with which the file was opened.
It is set by
.IB f .open
(see below).
.TP
.B uname
The name of the user that created the fid.
.TP
.B param
Set by
.B Styxservers
to the
.B aname
of the initial
.IR attach (5)
message,
and subsequently inherited by each new fid created by
.IR walk (5),
but not otherwise used by
.B Styxservers
itself, and may be changed by the application.
.TP
.B data
Unused by
.BR Styxservers ;
for application use.
It might be used, for instance, to implement a file that gives different
data to different clients.
.TP
.IB f .clone( nf )
Copy the current state of all members of
.I f
except
.IB f .fid\f1,\fP
into
.IR nf ,
and return
.IR nf .
Used by
.BR Styxserver.walk ,
and is needed by an application only if it replaces that function.
.TP
.IB f .walk( qid )
Make
.I f
refer to the file with the given
.IR qid :
set
.IB f .path
and
.IB f .qtype
from
.IB qid .path
and
.IB qid .qtype .
Used by
.IB Styxserver.walk
and is needed by an application only if it replaces that function.
.TP
.IB f .open( mode,\ qid )
Mark
.I f
as `open',
set
.IR f .mode
to
.IR mode ,
and set
.B path
and
.B qtype
to the path and type of
.IR qid .
Used by the
implementations of
.B open
and
.B create
messages.
The default implementation of
.IR open (5)
in
.B Styxserver
obtains the value of
.I mode
from
.B Styxserver.canopen
(below),
and
obtains the value of
.I qid
by querying the application's navigator.
.SS Styxserver and file server state
Each
.B Styxserver
value holds the state for a single file server, including its active fids,
the link to the external name space process, and other internal data.
Most of the state is manipulated through the member functions described below.
The exceptions are two read-only values:
the
.B Navigator
reference
.IB srv .t
which can be used to access that navigator; and
the file descriptor
.IB 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
.B Styxserver
value for convenience.
The file descriptor value is normally used only through
.BR Styxserver.reply ,
but will be needed directly if the caller needs the file descriptor value
as a parameter to
.IR sys-pctl (2)
when insulating the serving process's file descriptors from the surrounding environment.
.PP
The first set of functions in
.B Styxserver
provides common and default actions:
.TP
.B Styxserver.new(\fIfd\fP,\ \fIt\fP,\ \fIrootpath\fP)
Create a new
.BR Styxserver .
It returns a tuple, say
.RI ( c ", " srv ),
and spawns a new process, which uses
.IR styx (2)
to read and parse 9P messages read
from
.IR fd ,
and send them down
.IR c ;
.I t
should be a
.B Navigator
adt which the
.B Styxserver
can use to answer queries
on the name space (see ``Navigating file trees'', below).
.I Rootpath
gives the Qid path of the root of the served name space.
.TP
.IB srv .reply(\fIm\fP)
Send a reply (R-message) to a client. The various utility methods,
listed below, call this function to make their response. When
.IB 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
.IB srv .replydirect
when appropriate.
.TP
.IB srv .attach(\fIm\fP)
Respond to an
.IR attach (5)
message
.IR m ,
creating a new fid in the process, and returning it.
Returns
.B nil
if
.IB m .fid
is a duplicate of an existing fid.
The value of the attach parameter
.IB m .aname
is copied into the new fid's
.B param
field, as is the attaching user name,
.IB m .uname .
.TP
.IB srv .clunk(\fIm\fP)
Respond to a
.IR clunk (5)
message
.IR m ,
and return the old
.BR Fid .
Note that this does nothing about remove-on-close
files; that should be programmed explicitly if needed.
.TP
.IB srv .walk(\fIm\fP)
Respond to a
.IR walk (5)
message
.IR m ,
querying
.IB srv . t
for information on existing files.
.TP
.IB srv .open(\fIm\fP)
Respond to an
.IR open (5)
message
.IR m .
This will allow a file to be opened if its permissions allow the
specified mode of access.
.TP
.IB srv .read(\fIm\fP)
Respond to a
.IR read (5)
message
.IR m .
If a directory is being read, the appropriate reply
is made; for files, an error is given.
.TP
.IB srv .remove(\fIm\fP)
Respond to a
.IR remove (5)
message
.IR m
with an error, clunking the fid as it does so,
and returning the old
.BR Fid .
.TP
.IB srv .stat(\fIm\fP)
Respond to a
.IR stat (5)
message
.IR m .
.TP
.IB srv .default(\fIgm\fP)
Respond to an arbitrary T-message,
.IR gm ,
as appropriate (eg, by calling
.IB srv .walk
for a
.IR walk (5)
message).
It responds appropriately to
.IR version (5),
and replies to
.B Tauth
(see
.IR attach (5))
stating that authentication is not required.
Other messages without an associated
.B Styxserver
function are generally responded to
with a ``permission denied'' error.
.PP
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
.IR error (5)
message on error.
.PP
The following further
.B Styxserver
operations are useful
in applications that override all or part of the default handling
(in particular,
to process read and write requests):
.TP
.IB srv .canopen( m )
Check whether it is legal to open a file as requested by message
.IR m :
the fid is valid but not already open, the corresponding file exists and its
permissions allow access in the requested mode, and if
.B Sys->ORCLOSE
is requested, the parent directory is writable (to allow the file to be removed when closed).
.B Canopen
returns a tuple, say
.RI ( f ,\  mode ,\  d,\ err\ \fP).
If the open request was invalid,
.I f
will be nil, and the string
.I err
will diagnose the error (for return to the client in an
.B Rmsg.Error
message).
If the request was valid:
.I f
contains the
.B Fid
representing the file to be opened;
.I mode
is the access mode derived from
.IB m .mode ,
.BR Sys->OREAD ,
.BR Sys->OWRITE ,
.BR Sys->ORDWR ,
ORed with
.BR Sys->ORCLOSE ;
.I d
is a
.B Dir
value giving the file's attributes, obtained from the navigator;
and
.I err
is nil.
Once the application has done what it must to open the file,
it must call
.IB f .open
to mark it open.
.TP
.IB srv .cancreate( m )
Checks whether the
creation of the file requested by
message
.I m
is legal:
the fid is valid but not open, refers to a directory,
the permissions returned by
.IR 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.
.B Cancreate
returns a tuple, say
.RI ( f ,\  mode,\  d,\ err\ \fP).
If the creation request was invalid,
.I f
will be nil, and the string
.I err
will diagnose the error, for use in an error reply to the client.
If the request was valid:
.I f
contains the
.B Fid
representing the parent directory;
.I mode
is the open mode as defined for
.B canopen
above;
.I d
is a
.B Dir
value containing some initial attributes for the new file or directory;
and
.I err
is nil.
The initial attributes set in
.I d
are:
.IB d .name
(the name of the file to be created);
.IB d .uid
and
.IB d .muid
(the user that did the initial attach);
.IB d .gid ,
.IB d .dtype ,
.IB d .dev
(taken from the parent directory's attributes);
and
.IB d .mode
holds the file mode that should be attributed to the new
file (taking into account the parent mode, as
described in
.IR open (5)).
The caller must supply
.IB d .qid
once the file has successfully been created,
and
.IB d .atime
and
.IB d .mtime ;
it must also call
.IB f .open
to mark
.I f
open and set its path to the file's path.
If the file cannot be created successfully, the application should reply with
an
.IR error (5)
message and leave
.I f
untouched.
The
.B Fid
.I f
will then continue to refer to the original directory, and remain unopened.
.TP
.IB srv .canread( m )
Checks whether
.IR read (5)
message
.I m
refers to a valid fid that has been opened for reading,
and that the count and file offset are non-negative.
.B Canread
returns a tuple, say
.RI ( f ,\  err );
if the attempted access is illegal,
.I f
will be nil, and
.I err
contains a description of the error,
otherwise
.I f
contains the
.B Fid
corresponding to the file in question.
It is typically called by an application's implementation of
.B Tmsg.Read
to obtain the
.B Fid
corresponding to the fid in the message, and check the access.
.TP
.IB srv .canwrite( m )
Checks whether
message
.I m
refers to a valid fid that has been opened for writing,
and that the file offset is non-negative.
.B Canwrite
returns a tuple
.RI ( f ,\  err );
if the attempted access is illegal,
.I f
will be nil, and
.I err
contains a description of the error,
otherwise
.I f
contains the
.B Fid
corresponding to the file in question.
It is typically called by an application's implementation of
.B Tmsg.Write
to obtain the
.B Fid
corresponding to the fid in the message, and check the access.
.TP
.IB srv .canremove( m )
Checks whether the
removal of the file requested by
message
.I m
is legal: the fid is valid, it is not a root directory, and there is write permission
in the parent directory.
.B Canremove
returns a tuple
.RI ( f,\ path,\ err );
if the attempted access is illegal,
.I f
will be nil and
.I err
contains a description of the error;
otherwise
.I f
contains the
.B Fid
for the file to be removed, and
.I path
is the
.B Qid.path
for the parent directory.The caller should remove the file, and in every case must call
.B srv.delfid
before replying,
because the protocol's remove operation always clunks the fid.
.TP
.IB srv .iounit()
Return an appropriate value for use as the
.I iounit
element in
.B Rmsg.Open
and
.B Rmsg.Create
replies,
as defined in
.IR open (5),
based on the message size negotiated by the initial
.IR version (5)
message.
.PP
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
.B Tmsg
messages and the
.B Fid
values that represent the corresponding files internally.
.TP
.IB srv .newfid(\fIfid\fP)
Create a new
.B Fid
associated with number
.I fid
and return it.
Return nil if the
.I fid
is already in use (implies a client error if the server correctly clunks fids).
.TP
.IB srv .getfid(\fIfid\fP)
Get the
.B Fid
data associated with numeric id
.IR fid ;
return nil if there is none such (a malicious or erroneous client
can cause this).
.TP
.IB srv .delfid(\fIfid\fP)
Delete
.I fid
from the table of fids in the
.BR Styxserver .
(There is no error return.)
.TP
.IB srv .allfids()
Return a list of all current fids (ie, the files currently active on the client).
.PP
.B Newfid
is required when processing
.IR auth (5),
.IR attach (5)
and
.IR walk (5)
messages to create new fids.
.B Delfid
is used to clunk fids when processing
.IR clunk (5),
.IR remove (5),
and in a failed
.IR walk (5)
when it specified a new fid.
All other messages should refer only to already existing fids, and the associated
.B Fid
data is fetched by
.BR getfid .
.SS Navigating file trees
When a
.B Styxserver
instance needs to know about the namespace,
it queries an external process through a channel
by sending a
.B Navop
request;
each such request carries with it a
.B reply
channel through which the
reply should be made.
The reply tuple has a reference to a
.B Sys->Dir
value that is non-nil on success, and a diagnostic string
that is non-nil on error.
.PP
Files in the tree are referred to
by their Qid
.BR path .
The requests are:
.TF Walk
.TP
.BR Stat
.br
Find a file in the hierarchy by its
.BR path ,
and reply with the corresponding
.B Dir
data if found (or a diagnostic on error).
.TP
.BR Walk
.br
Look for file
.B name
in the directory with the given
.BR path .
.TP
.BR Readdir
.br
Get information on selected files in the directory with the given
.BR path .
In this case, the reply channel is used to send
a sequence of values, one for each entry in the directory, finishing with a tuple value
.BR (nil,nil) .
The entries to return are those selected by an
.B offset
that is the index (origin 0) of the first directory entry to return,
and a
.B count
of a number of entries to return starting with that index.
Note that both values are expressed in units of directory entries, not as byte counts.
.PP
.B Styxserver
provides a
.B Navigator
adt to enable convenient access to this functionality; calls
into the
.B Navigator
adt are bundled up into requests on the channel, and the
reply returned.
The functions provided are:
.TP 10
.BI Navigator.new( c )
Create a new
.BR Navigator ,
sending requests down
.IR c .
.TP
.IB t .stat(\fIpath\fP)
Find the file with the given
.IR path .
Return a tuple
.RI ( d ,\  err ),
where
.I d
holds directory information for the file
if found; otherwise
.I err
contains an error message.
.TP
.IB t .walk(\fIparent\fP,\ \fIname\fP)
Find the file with name
.I name
inside parent directory
.IR parent .
Return a tuple as for
.BR stat .
.TP
.IB t .readdir(\fIpath\fP,\ \fIoffset\fP,\ \fIcount\fP)
Return directory data read from directory
.IR path ,
starting at entry
.I offset
for
.I count
entries.
.SS Other functions
The following functions provide some commonly used functionality:
.TP 10
.BI readbytes( m ,\  d )
Assuming that the file in question contains data
.IR d ,
.B readbytes
returns an appropriate reply to
.IR read (5)
message
.IR m ,
taking account of
.IB m .offset
and
.IB m.count
when extracting data from
.IR d .
.TP 10
.BI readstr( m ,\  s )
Assuming that the file in question contains string
.IR s ,
.B readstr
returns an appropriate reply to
.IR read (5)
message
.IR m ,
taking account of
.IB m .offset
and
.IB m.count
when extracting data from the UTF-8 representation of
.IR s .
.TP
.BI openok (\fIuname\fP,\ \fIomode\fP,\ \fIperm\fP,\ \fIfuid\fP,\ \fIfgid\fP)
Does standard permission checking, assuming user
.I uname
is trying to open a file with access mode
.IR omode ,
where the file is owned by
.IR fuid ,
has group
.IR fgid ,
and permissions
.IR perm .
Returns true (non-zero) if permission would be granted, and false (zero) otherwise.
.TP
.BI openmode( o )
Checks to see whether the open mode
.I o
is well-formed; if it is not,
.B openmode
returns -1; if it is, it returns the mode
with OTRUNC and ORCLOSE flags removed.
.TP
.BI traceset( on )
If
.I on
is true (non-zero),
will trace 9P requests and replies, on standard error.
This option must be set before creating a
.BR Styxserver ,
to ensure that it preserves its standard error descriptor.
.SS Constants
.B Styxservers
defines a number of constants applicable to the writing
of 9P servers, including:
.TP
.BR Einuse\fP,\fP\ Ebadfid\fP,\fP\ Eopen\fP,\fP\ Enotfound\fP,\fP\ Enotdir\fP,\fP\ Eperm\fP,\fP\ Ebadarg\fP,\fP\ Eexists
These provide standard strings for commonly used error conditions,
to be used in
.B Rmsg.Error
replies.
.SS Authentication
If authentication is required beyond that provided at the link level
(for instance by
.IR security-auth (2)),
the server application must handle
.B Tauth
itself,
remember the value of
.I afid
in that message, and generate an
.B Rauth
reply with a suitable Qid referring to a file with
.B Qid.qtype
of
.BR QTAUTH .
Following successful authentication by read and write on that file,
it must associate that status with the
.IR afid .
Then, on a subsequent
.B Tattach
message, before calling
.I srv .attach
it must check that the
.BR Tattach 's
.I afid
value corresponds to one previously authenticated, and
reply with an appropriate error if not.
.SH SOURCE
.B /appl/lib/styxservers.b
.SH SEE ALSO
.IR styxservers-nametree (2),
.IR sys-stat (2),
.IR intro (5)