summaryrefslogtreecommitdiff
path: root/appl/spree/man
diff options
context:
space:
mode:
Diffstat (limited to 'appl/spree/man')
-rw-r--r--appl/spree/man/gamesrv.man2471
-rw-r--r--appl/spree/man/gamesrv.man4296
-rw-r--r--appl/spree/man/styxservers-nametree.man2180
-rw-r--r--appl/spree/man/styxservers.man2902
4 files changed, 1849 insertions, 0 deletions
diff --git a/appl/spree/man/gamesrv.man2 b/appl/spree/man/gamesrv.man2
new file mode 100644
index 00000000..fd910519
--- /dev/null
+++ b/appl/spree/man/gamesrv.man2
@@ -0,0 +1,471 @@
+.TH GAMESRV 2
+.SH NAME
+Gamesrv \- game server module
+.SH SYNOPSIS
+.EX
+.ps -1
+.vs -1
+include "draw.m";
+include "gamesrv.m";
+gamesrv := load Gamesrv Gamesrv->PATH;
+Range, Object, Game, Player: import gamesrv;
+
+Range: adt {
+ start: int;
+ end: int;
+};
+
+Object: adt {
+ transfer: fn(o: self ref Object,
+ r: Range, dst: ref Object, i: int);
+ setvisibility: fn(o: self ref Object,
+ visibility: int);
+ setattrvisibility: fn(o: self ref Object,
+ name: string, visibility: int);
+ setattr: fn(o: self ref Object,
+ name: string, val: string, vis: int);
+ getattr: fn(o: self ref Object, name: string): string;
+ delete: fn(o: self ref Object);
+ deletechildren: fn(o: self ref Object, r: Range);
+
+ id: int;
+ parentid: int;
+ children: array of ref Object;
+ objtype: string;
+ visibility: int;
+ # ...private data
+
+};
+
+Game: adt {
+ newobject: fn(game: self ref Game, parent: ref Object,
+ visibility: int, objtype: string): ref Object;
+ action: fn(game: self ref Game, cmd: string,
+ objs: list of int, rest: string, whoto: int);
+ player: fn(game: self ref Game, id: int): ref Player;
+
+ objects: array of ref Object;
+ # ...private data
+};
+
+Player: adt {
+ name: fn(player: self ref Player): string;
+ hangup: fn(player: self ref Player);
+ obj: fn(player: self ref Player, id: int): ref Object;
+
+ id: int;
+ # ...private data
+};
+
+Gamemodule: module {
+ clienttype: fn(): string;
+ init: fn(game: ref Gamesrv->Game, srvmod: Gamesrv): string;
+ command: fn(player: ref Gamesrv->Player, e: string): string;
+ join: fn(player: ref Gamesrv->Player): string;
+ leave: fn(player: ref Gamesrv->Player);
+};
+
+rand: fn(n: int): int;
+.ps +1
+.vs +1
+.EE
+.SH DESCRIPTION
+.I Gamesrv
+provides a general server interface that allows distributed
+clients to interact in a controlled manner, with the
+interaction mediated
+by Limbo modules, known as
+.IR "game engines" ,
+or just
+.I engines
+for short.
+Each engine decides on the rules
+of its particular game; the engine interface is described
+at the end of this manual page, under
+``Module Interface''.
+.PP
+This manual page describes the
+interface as presented to an engine
+once it has been loaded by
+.IR gamesrv .
+An engine is responsible for a particular
+.IR game ,
+in which one or more
+.I players
+participate. Messages sent by players
+are interpreted by the game engine, which
+responds by making changes to the hierarchical
+.I object
+database held by the game.
+Behind the scenes
+.I gamesrv
+distributes updates to this database to players
+of the game as appropriate.
+.SS "Objects and visibility"
+Objects hold a game's visible state. An object
+has a unique integer
+.IR id ,
+which is an index into the array
+.IB game .objects\fR;\fP
+it also holds a set of attribute-value pairs, a type, and
+zero or more child objects. Together, all the objects
+in the game form a hierarchical tree, rooted at
+the
+.IR "root object"
+(id 0), which always exists.
+Each attribute and each object also has an associated
+.IR "visibility set" ,
+the set of players that sees updates to the attribute or the children
+of the object. A visibility set is an integer, a bitmask where each
+bit represents one player, hence
+.B ~0
+is visible to all players, and
+.B 0
+is visible to no-one.
+In general, each player has a unique
+identifier
+.IR id ;
+in an integer
+.I i
+representing a set of players,
+the
+.IR id th
+bit represents the presence of the player with
+identifier
+.IR id .
+Thus, for a player
+.IR p ,
+.BI "(1<<" p ".id)"
+is the set containing only
+.IR p ,
+.BI "(" i "&~(1<<" p ".id))"
+excludes
+.I p
+from the set, and
+.BI "(" i "|(1<<" p ".id))"
+includes
+.I p
+in the set.
+.PP
+Note that the visibility set of an object does not alter the visibility
+of that object's attributes, but only that of its children (and of
+their children: in general an object is visible to a player if the
+intersection of all its ancestors' visibility sets contains that
+player).
+.PP
+Objects can be transferred inside the hierarchy from one parent to
+another. If an object is moved to a parent whose visibility conceals it
+from a player, then it will appear to that player to have been deleted;
+if it is later made visible, then it will be recreated for that
+player.
+A game engine can almost always ignore this technicality,
+except for one thing: the identifier used by a particular player to
+identify an object is not necessarily the same as that used by the game
+engine. Thus when an engine receives an object id in a player's
+message, it should convert it using the
+.IB player .obj()
+function.
+.SS \fBGame\fP
+The
+.B Game
+type holds all the objects in a game. It allows the
+creation of new objects, and provides way of communicating
+with players outside the object hierarchy.
+All data members of a
+.B Game
+should be treated as read-only.
+.TP 10
+.IB game .objects
+This array holds the objects in the game. An object with
+identifier
+.I id
+is found at
+.IB game .objects[ id ]\fR.\fP
+.TP
+.IB game .newobject(\fIparent\fP,\ \fIvisibility\fP,\ \fIobjtype\fP)
+.B Newobject
+creates a new object at the end
+of
+.IR parent 's
+children;
+If
+.I parent
+is nil, the new object is created under the root object.
+The new object has visibility
+.IR visibility ,
+and type
+.IR objtype .
+An object's type cannot be changed once
+it has been created.
+.TP
+.IB game .action(\fIcmd\fP,\ \fIobjs\fP,\ \fIrest\fP,\ \fIwhoto\fP)
+.B Action
+sends a message to some players without affecting
+the object hierarchy. It can be used to send transient
+events that have no meaning when stored statically
+(for example, network latency probes).
+The message is sent to the set of players given by
+.IR whoto .
+.I Objs
+is assumed to be a list of object ids, which are
+converted appropriately for each player
+receiving the message; the final
+message is a string built by concatenating
+.IR cmd ,
+the list of object ids, and
+.IR rest ,
+separated by spaces.
+.TP
+.IB game .player(\fIid\fP)
+.B Player
+yields the player corresponding to identifier
+.IR id ,
+or
+.B nil
+if there is none.
+.SS Player
+The
+.B Player
+type represents a player of a game.
+.TP 10
+.IB player .id
+The player's identifier, an integer between
+0 and 31. This is unique across all current players,
+but ids of players that have left the game will
+be reused.
+.TP
+.IB player .obj(\fIid\fP)
+.B Obj
+converts from a player's external object
+identifier to the game's local
+.B Object
+that it represents. It returns
+.B nil
+if there is no such object.
+.TP
+.IB player .hangup()
+.B Hangup
+hangs up a player's connection to the game;
+no more requests from
+.I player
+will be received by the game engine.
+.TP
+.IB player .name()
+.B Name
+yields the authenticated name of the player.
+This is not necessarily unique over the players
+of a game.
+.SS \fBObject\fP
+The
+.B Object
+type is the basic unit of game engine state.
+An object's children can be selectively concealed
+from players; it holds a set of
+.RI ( attribute ,\ value )
+pairs, each of which can be concealed likewise.
+Where an argument
+.IR r ,
+of
+.B Range
+type is used, it refers to a range of an object's
+children starting at index
+.IB r .start\fR,\fP
+and finishing at
+.IB r .end-1\fR.\fP
+All the data members of an
+.B Object
+should be treated as read-only.
+.TP 10
+.IB obj .setattr(\fIname\fP,\ \fIval\fP,\ \fIvis\fP)
+.B Setattr
+sets attribute
+.I name
+in
+.I obj
+to
+.IR val.
+If the attribute is being created for the
+first time, then it will be given visibility
+.IR vis .
+.I Name
+should be non-empty, and should not
+contain any space characters.
+Note that it is not possible for an attribute
+to refer directly to an object by its identifier;
+if this facility is needed, another identifying
+scheme should be used. This also applies
+to player identifiers, which will change
+if the game is saved and loaded again (not
+implemented yet).
+.TP
+.IB obj .getattr(\fIname\fP)
+.B Getattr
+yields the current value of the
+attribute
+.I name
+in
+.IR obj .
+If an attribute is not set, it yields
+.BR nil .
+.TP
+.IB obj .delete()
+.B Delete
+removes
+.I obj
+from the object
+hierarchy.
+.TP
+.IB obj .deletechildren(\fIr\fP)
+.B Deletechildren
+deletes children in range
+.I r
+from
+.IR obj .
+.TP
+.IB obj .transfer(\fIr\fP,\ \fIdst\fP,\ \fIi\fP)
+.B Transfer
+transfers the children in range
+.I r
+from
+.I obj
+to just before the object at index
+.I i
+in
+.IR dst .
+It is permissible for
+.I obj
+and
+.I dst
+to be the same object.
+.TP
+.IB obj .setvisibility(\fIvisibility\fP)
+.B Setvisibility
+allows the set of players
+given in
+.I visibility
+to see the children of
+.IR obj ,
+and denies access to all others.
+Players are notified of the change.
+.TP
+.IB obj .setattrvisibility(\fIname\fP,\ \fIvisibility\fP)
+.B Setattrvisibility
+allows the set of players
+given in
+.I visibility
+to see the value of
+.IR obj 's
+attribute
+.IR name ,
+and denies access to all others.
+Players are not notified of the change;
+if there is a need to communicate
+the fact of an attribute becoming invisible to
+players, it should be done by using another
+(visible) attribute to communicate the change.
+.SS "Module Interface"
+A game engine module,
+.IR mod ,
+must implement the
+following functions. Where a function returns a string,
+it is interpreted as an error response to the player
+responsible for the request; an empty string signifies
+no error.
+.TP
+.IB mod .clienttype()
+.B Clienttype
+should return the type of client required
+by the engine (e.g.
+.B cards
+for the card-game client).
+Each client type has its own conventions
+as to the meaning of object types and attribute
+names and values.
+This function may be called before
+.BR init() .
+.TP
+.IB mod .init(\fIgame\fP,\ \fIsrvmod\fP)
+.B Init
+initialises the game engine.
+.I Game
+is the game that the engine is controlling,
+and
+.I srvmod
+is the
+.B Gamesrv
+module holding its associated data.
+An error response from this function
+causes the game to be aborted.
+.TP
+.IB mod .join(\fIplayer\fP)
+.I Player
+has made a request to join the game;
+an error response causes the request to be
+refused, otherwise the player joins the
+game.
+.TP
+.IB mod .leave(\fIplayer\fP)
+.I Player
+has left the game.
+.TP
+.IB mod .command(\fIplayer\fP,\ \fIe\fP)
+.I Player
+has sent the command
+.IR e .
+The command usually follows
+the simple message conventions
+used in
+.IR gamesrv (4),
+i.e. simple space-separated tokens.
+.SH EXAMPLE
+The following is a small, but working example
+of a game engine that acts as a chat server
+(parsing error checking omitted, and white-space
+compressed to save paper):
+.PP
+.EX
+.ps -1
+.vs -1
+implement Gamemodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "../gamesrv.m";
+ gamesrv: Gamesrv;
+ Game, Player: import gamesrv;
+game: ref Game;
+clienttype(): string
+{
+ return "chat";
+}
+init(g: ref Game, srvmod: Gamesrv): string
+{
+ (sys, game, gamesrv) = (load Sys Sys->PATH, g, srvmod);
+ return nil;
+}
+join(nil: ref Player): string
+{
+ return nil;
+}
+leave(nil: ref Player)
+{
+}
+command(player: ref Player, cmd: string): string
+{
+ game.action("say " + string player.id + " " + cmd, nil, nil, ~0);
+ return nil;
+}
+.ps +1
+.vs +1
+.EE
+.SH SOURCE
+.B /appl/cmd/games/gamesrv.b
+.SH "SEE ALSO"
+.IR gamesrv (4)
+.SH BUGS
+The reuse of object ids can lead to
+problems when objects are deleted and
+recreated on the server before clients become
+aware of the changes.
+.PP
+This interface is new and will change.
diff --git a/appl/spree/man/gamesrv.man4 b/appl/spree/man/gamesrv.man4
new file mode 100644
index 00000000..5db352b3
--- /dev/null
+++ b/appl/spree/man/gamesrv.man4
@@ -0,0 +1,296 @@
+.TH GAMESRV 4
+.SH NAME
+gamesrv \- game server
+.SH SYNOPSIS
+.B games/gamesrv
+[
+.B -l
+] [
+.B -a
+.I alg
+]...
+[
+.B -A
+] [
+.IR addr | mntpoint
+]
+.PP
+.IB mntpoint /players
+.br
+.IB mntpoint /new
+.br
+.IB mntpoint / n
+.SH DESCRIPTION
+.B Gamesrv
+serves a file system that allows clients to interact
+through various types of game engine.
+Usually, it operates in network mode:
+it listens for incoming connections on
+.I addr
+(default
+.BR tcp!*!3242 ),
+authenticates them, and serves files to them.
+If the
+.B -A
+option is given, no authentication takes place,
+otherwise each
+.I alg
+gives an additional possible
+encryption or digest algorithm to use
+on the connection (see
+.IR ssl (3)).
+If no
+.I alg
+is specified,
+.B none
+is assumed.
+The
+.B -l
+option causes the game server to be mounted
+locally on
+.I mntpoint
+\- this can be useful for single player games,
+or debugging.
+.PP
+Once the name-space served by
+.I gamesrv
+is mounted, it serves the following files.
+All identifiers referred to below are
+small integers, expressed as decimal ASCII strings.
+.TP
+.B players
+Reading this file provides updates on players
+arriving and leaving, games being created
+and destroyed, and chat messages outside
+the scope of any game.
+Reads will block until something of interest happens.
+Each update holds space separated
+tokens and is terminated with a newline.
+A read will return as many updates as will fit
+into the read buffer. Update messages are as follows:
+.RS
+.TP
+.BI clientid " clientid name"
+Identifies the name,
+.IR name ,
+and the client identifier,
+.IR clientid ,
+of the client
+reading the players file.
+.TP
+.BI join " clientid name"
+A client has authenticated as
+.IR name ,
+and has been allocated identifier
+.IR clientid .
+.TP
+.BI leave " clientid"
+The client identified by
+.I clientid
+has terminated connection with the server.
+.TP
+.BI gametype " clienttype name"
+The server announces the availability of a game
+named
+.I name
+on the server. The game requires a client of
+type
+.I clienttype
+to display the game.
+.TP
+.BI creategame " gameid name clienttype"
+An instance of a game named
+.IR name
+has been created; it needs a client
+of type
+.IR clienttype ,
+and has been given identifier
+.IR gameid .
+.TP
+.BI deletegame " gameid"
+The game identified by
+.I gameid
+has been deleted.
+.TP
+.BI joingame " gameid clientid playerid name"
+Client
+.I clientid
+(named
+.IR name )
+has joined game
+.I gameid ,
+and is allocated player id
+.I playerid
+in the game.
+.TP
+.BI leavegame " gameid playerid name"
+Player
+.I playerid
+(named
+.IR name )
+has left
+.IR gameid .
+.TP
+.BI chat " clientid msg"
+Client
+.I clientid
+has sent the chat message
+.IR msg .
+.PP
+Writing to the
+.B players
+file causes a
+.B chat
+message to be sent to all other clients reading
+the players file. All but the first line of the
+write request is ignored.
+.RE
+.TP
+.B new
+Opening
+.B new
+prepares to create a new game.
+The only message that can be written
+to a newly opened game is
+.BI \fR``\fPcreate " name"\fR'',\fP
+to request a new game named
+.IR name .
+The write request draws an error
+if
+.I gamesrv
+fails to find and load the requisite game
+engine.
+If the write succeeds, the game is created,
+and game updates can be read in the same
+manner as from the
+.B players
+file. The update messages are as follows:
+.RS
+.TP
+.BI playerid " clientid playerid name"
+Identifies the player identifier,
+.IR playerid ,
+and name,
+.IR name ,
+of the reader.
+.TP
+.BI create " objid parentid visibility objtype"
+Create an object, identified by
+.IR objid ,
+at the end of
+.IR parentid 's
+children
+.RI ( parentid
+is
+.B -1
+for the root object).
+.I Visibility
+is the visibility set of the object (see
+.IR gamesrv (2)),
+and
+.I objtype
+is its type.
+.TP
+.BI tx " srcid dstid start end index"
+Transfer objects from
+.I srcid
+to
+.IR dstid.
+Take the objects from the range
+.RI [ start ,\ end )
+in the children of
+.IR srcid ,
+and insert them just before
+.I index
+in
+.IR dstid .
+Note that when objects are transferred
+to an object that conceals its children,
+and the object is itself visible,
+the objects will first be transferred to the
+destination and then deleted; objects transferred
+out of such an object will first be created and
+.I then
+transferred to their destination.
+This enables a client to maintain some knowledge
+of where an object has been transferred to, even
+if the object is no longer visible.
+.TP
+.BI del " parentid start end"
+Delete the range
+.RI [ start ,\ end )
+of children from the object identified by
+.IR parentid .
+.I Gamesrv
+guarantees that those objects will themselves
+not have any children.
+.TP
+.BI set " objid attr val"
+Set the attribute named
+.I attr
+on object
+.I objid
+to
+.IR val .
+.TP
+.BI vis " objid visibility"
+The visibility of object
+.I objid
+has changed to
+.IR visibility .
+.TP
+.I action
+Game engines can generate arbitrary messages
+of their own devising; such messages are specific
+to particular client types.
+.PP
+Note that a given client does not have to interpret
+all the above messages \- different client types
+have their own conventions. The
+.B card
+client type uses most of the above functionality,
+for example, whereas a client for the
+.B chat
+engine listed in
+.IR gamesrv (2)
+can get away with interpreting only one message, the custom action
+.BR chat .
+.PP
+Writes to the opened game file
+are interpreted as game actions by
+the game that has been loaded, and acted on accordingly.
+Invalid actions will draw a write error.
+.RE
+.TP
+.I n
+Once a game has been created, it appears as
+a numbered file, corresponding to the
+.I gameid
+of the game in question.
+Opening this file joins the game; reads and writes
+work as for the
+.B new
+file, above.
+A single client cannot join a particular game
+more than once.
+.PP
+A zero-length write to any file causes any reads
+of that file from the same file descriptor to yield
+EOF (no bytes).
+This is necessary to force a hangup under
+systems such as Windows, where it is not possible
+to interrupt a kproc blocked on a network read.
+.SH EXAMPLE
+The simplest client!
+.PP
+.EX
+mount tcp!somehost.com!3242 /n/remote
+{
+ echo create chat >[1=0]
+ cat &
+ cat >[1=0] < /dev/cons
+} <> /n/remote/new
+.SH SOURCE
+.B /appl/cmd/games/gamesrv.b
+.SH SEE ALSO
+.IR gamesrv (2)
diff --git a/appl/spree/man/styxservers-nametree.man2 b/appl/spree/man/styxservers-nametree.man2
new file mode 100644
index 00000000..f64e519a
--- /dev/null
+++ b/appl/spree/man/styxservers-nametree.man2
@@ -0,0 +1,180 @@
+.TH STYXSERVERS-NAMETREE 2
+.SH NAME
+Styxservers: nametree \-
+hierarchical name storage for use with Styxservers.
+.SH SYNOPSIS
+.EX
+include "sys.m";
+include "styx.m";
+include "styxservers.m";
+nametree := load Nametree Nametree->PATH;
+ Tree: import nametree;
+
+Tree: adt {
+ create: fn(t: self ref Tree, parentpath: big, d: Sys->Dir): string;
+ remove: fn(t: self ref Tree, path: big): string;
+ wstat: fn(t: self ref Tree, path: big, d: Sys->Dir);
+ quit: fn(t: self ref Tree);
+};
+init: fn();
+start: fn(): (ref Tree, chan of ref Styxservers->Navop);
+.EE
+.SH DESCRIPTION
+.B Nametree
+provides the storage for a hierarchical namespace
+to be used by
+.IR styxservers (2).
+After the module is loaded, the
+.B init
+function should be called to
+initialise the module's internal variables.
+.B Start
+spawns a new
+.B nametree
+process; it returns a tuple, say
+.RI ( tree ,\ c ),
+where c is a channel that can be used to create
+an instance of
+.BR Styxservers->Navigator ,
+to access files inside
+.BR nametree ,
+and
+.I tree
+is an adt that allows creation and removal of those files.
+On failure, these functions return a string describing
+the error.
+.PP
+Note that the full set of operations on
+.B Nametree
+(i.e. stat, walk, readdir, wstate, create and remove),
+is only available in conjunction with
+.BR Styxserver 's
+.B Navigator
+interface.
+Files in the name space are ultimately identified by a 64-bit
+.I path
+value, which forms the path component of the file's Qid.
+(See
+.IR intro (5)
+for a description of the system's interpretation of Qids.)
+.PP
+The
+.B Tree
+operations
+are:
+.TP 10
+.IB t .create(\fIparentpath\fP,\ \fId\fP)
+Create a new file or directory.
+.I D
+gives the directory information that will be stored
+for the file, including its own path value,
+given by
+.IB d .qid.path .
+If the file referenced by
+.I parentpath
+does not exist, creation will not be allowed,
+other than in the special case when
+.IB d .qid.path
+is equal to
+.IR parentpath ,
+in which case it is assumed to be a root directory
+and may be created. This potentially allows a single
+.B Nametree
+instance to hold many distinct directory hierarchies.
+Note that no attempt is made to ensure that
+.I parentpath
+refers to a directory; the check is assumed to have
+been made previously.
+When a hierarchy is traversed,
+.B Nametree
+interprets the name
+.RB ` .. '
+itself as `parent directory', and that name should not be created explicitly.
+.TP
+.IB t .remove(\fIpath\fP)
+Remove the file referred to by
+.IR path ,
+and all its descendants.
+.TP
+.IB t .wstat(\fIpath\fP,\ \fId\fP)
+Change the directory information held on file
+.IR path .
+The Qid path itself cannot be changed by
+.IR d .
+.TP
+.IB t .quit()
+Shut down the
+.B nametree
+process.
+.SH EXAMPLE
+Here is a complete example that uses
+.B Nametree
+in conjunction with
+.B Styxservers
+in order to serve two files
+.B data
+and
+.BR ctl " ..."
+and do nothing with them:
+.EX
+implement Tst;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "styx.m";
+include "styxservers.m";
+ styxservers: Styxservers;
+ Styxserver, Navigator: import styxservers;
+ nametree: Nametree;
+ Tree: import nametree;
+
+Tst: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+Qroot, Qctl, Qdata: con big iota; # paths
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ styx := load Styx Styx->PATH;
+ styx->init();
+ styxservers = load Styxservers Styxservers->PATH;
+ styxservers->init(styx);
+ nametree = load Nametree Nametree->PATH;
+ nametree->init();
+ sys->pctl(Sys->FORKNS, nil);
+ (tree, treeop) := nametree->start();
+ tree.create(Qroot, dir(".", 8r555|Sys->DMDIR, Qroot));
+ tree.create(Qroot, dir("ctl", 8r666, Qctl));
+ tree.create(Qroot, dir("data", 8r444, Qdata));
+ (tchan, srv) := Styxserver.new(sys->fildes(0),
+ Navigator.new(treeop), Qroot);
+ while((gm := <-tchan) != nil) {
+ # normally a pick on gm would act on
+ # Tmsg.Read and Tmsg.Write at least
+ srv.default(gm);
+ }
+ tree.quit();
+}
+
+dir(name: string, perm: int, qid: big): Sys->Dir
+{
+ d := sys->zerodir;
+ d.name = name;
+ d.uid = "me";
+ d.gid = "me";
+ d.qid.path = qid;
+ if (perm & Sys->DMDIR)
+ d.qid.qtype = Sys->QTDIR;
+ else
+ d.qid.qtype = Sys->QTFILE;
+ d.mode = perm;
+ return d;
+}
+.EE
+.SH SOURCE
+.B /appl/lib/nametree.b
+.SH SEE ALSO
+.IR styxservers (2),
+.IR intro (5)
diff --git a/appl/spree/man/styxservers.man2 b/appl/spree/man/styxservers.man2
new file mode 100644
index 00000000..fca2748a
--- /dev/null
+++ b/appl/spree/man/styxservers.man2
@@ -0,0 +1,902 @@
+.TH STYXSERVERS 2
+.SH NAME
+styxservers \-
+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 Styx 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);
+
+ # 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);
+
+ # 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 Styx 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 Styx 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 Styx 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 Styx 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.
+.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, 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.Write
+to obtain the
+.B Fid
+corresponding to the fid in the message, and check the access.
+.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 Styx 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 Styx 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)