diff options
Diffstat (limited to 'appl/spree/man')
| -rw-r--r-- | appl/spree/man/gamesrv.man2 | 471 | ||||
| -rw-r--r-- | appl/spree/man/gamesrv.man4 | 296 | ||||
| -rw-r--r-- | appl/spree/man/styxservers-nametree.man2 | 180 | ||||
| -rw-r--r-- | appl/spree/man/styxservers.man2 | 902 |
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) |
