ref: dd16eef8365cb0b9e4805c23291aadcaa596dd87
dir: /man/2/styxservers/
.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)