9P(3)
π 9P(3) β Library Functions Manual β¨
okay bestie, so this is 9P(3) from Library Functions Manual, and it's literally the whole vibe of how you build a 9P file service. it's a long one so buckle up π
πΈ NAME πΈ
Srv, dirread9p, emalloc9p, erealloc9p, estrdup9p, postfd, readbuf, readstr, respond, srv, threadpostmountsrv, walkandclone β 9P file service, i.e. the entire girlboss toolkit for serving files over 9P π
β¨ SYNOPSIS β¨
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
typedef struct Srv {
Tree* tree;
void (*attach)(Req *r);
void (*auth)(Req *r);
void (*open)(Req *r);
void (*create)(Req *r);
void (*read)(Req *r);
void (*write)(Req *r);
void (*remove)(Req *r);
void (*flush)(Req *r);
void (*stat)(Req *r);
void (*wstat)(Req *r);
void (*walk)(Req *r);
char* (*walk1)(Fid *fid, char *name, Qid *qid);
char* (*clone)(Fid *oldfid, Fid *newfid);
void (*destroyfid)(Fid *fid);
void (*destroyreq)(Req *r);
void (*start)(Srv *s);
void (*end)(Srv *s);
void* aux;
int infd;
int outfd;
int srvfd;
int nopipe;
} Srv;
int srv(Srv *s)
void threadpostmountsrv(Srv *s, char *name, char *mtpt, int flag)
int postfd(char *srvname, int fd)
void respond(Req *r, char *error)
ulong readstr(Req *r, char *src)
ulong readbuf(Req *r, void *src, ulong nsrc)
typedef int Dirgen(int n, Dir *dir, void *aux)
void dirread9p(Req *r, Dirgen *gen, void *aux)
void walkandclone(Req *r, char *(*walk1)(Fid *old, char *name, void *v),
char *(*clone)(Fid *old, Fid *new, void *v), void *v)
void* emalloc9p(ulong n)
void* erealloc9p(void *v, ulong n)
char* estrdup9p(char *s)
extern int chatty9p;
π¦ DESCRIPTION π¦
so. the function srv serves a whole 9P session. it reads requests from s->infd, dispatches them to the function pointers you stashed in Srv, and writes the responses out to s->outfd. no cap, that's the whole loop. (threadpostmountsrv is typically the one that sets up the infd and outfd struct members for you β more on that below π)
Req and Fid structures get allocated one-to-one with uncompleted requests and active fids respectively, and they're described over in 9p-fid(3).
the behavior of srv depends on whether there's a file tree associated with the server (see 9p-file(3)) β that is, on whether the tree element is nonzero. the differences get spelled out explicitly in the discussion of the service loop below. the aux element belongs to the client, to do with as it pleases π«Ά
srv does not return until the 9P conversation is finished. and because it's usually run in a separate process so the caller can just exit, the service loop has basically no chance to return gracefully on out-of-memory errors π. so it calls emalloc9p, erealloc9p, and estrdup9p to get its memory. the default implementations of these act as malloc, realloc, and strdup β but they abort the program if they run out of memory. if you want different behavior, clients can link against alternate implementations of these functions. your call, bestie.
threadpostmountsrv is the wrapper that creates a separate process in which to run srv. here's the whole routine, step by step:
- if
s->nopipeis zero (the common case), initializes->infdands->outfdto be one end of a freshly allocated pipe, withs->srvfdinitialized as the other end. - if
nameis non-nil, callpostfd(s->srvfd, name)to posts->srvfdas/srv/name. - fork a child process via
rfork(3)orprocrfork(seethread(3)), using theRFFDG,RFNAMEG, andRFMEMflags. the child process callsclose(s->srvfd)and thensrv(s); it will exit oncesrvreturns. - if
mtptis non-nil, callamount(s->srvfd, mtpt, flag, ""); otherwise, closes->srvfd. - the parent returns to the caller.
β οΈ if any error occurs during this process, the entire process is terminated by calling sysfatal(3). like, whole thing. gone. π
π Service functions π
the functions in a Srv structure named after 9P transactions get called to satisfy requests as they arrive. if you provide one, it must arrange for respond to be called when the request is satisfied. the only parameter of each service function is a Req* (say r). the incoming request parameters live in r->ifcall; r->fid and r->newfid are pointers to Fid structures corresponding to the numeric fids in r->ifcall; similarly, r->oldreq is the Req structure corresponding to r->ifcall.oldtag. outgoing response data should go in r->ofcall.
the one exception to this rule is that stat should fill in r->d rather than r->ofcall.stat β the library will convert the structure into the machine-independent wire representation for you β¨. similarly, wstat may consult r->d rather than decoding r->ifcall.stat itself.
when a request has been handled, respond should be called with r and an error string. if the request was satisfied successfully, the error string should be a nil pointer. note that it's totally permissible for a function to return without itself calling respond, as long as it has arranged for respond to be called at some point in the future by another proc sharing its address space β but see the discussion of flush below. once respond has been called, the Req* as well as any pointers it once contained must be considered freed and not referenced. like, don't touch it. it's gone. π
if the service loop detects an error in a request (e.g. an attempt to reuse an extant fid, an open of an already open fid, a read from a fid opened for write, etc.) it will reply with an error without consulting the service functions. it handles that itself, main character energy.
the service loop provided by srv (and indirectly by threadpostmountsrv) is single-threaded. so if you expect some requests might block, arranging for alternate processes to handle them is suggested π
the constraints on the service functions are as follows. these constraints are checked while the server executes. if a service function fails to do something it ought to have, srv will call end and then abort. harsh but fair.
authβ if authentication is desired, theauthfunction should record thatafidis the new authentication fid and setafid->qidandofcall.qid.authmay be nil, in which case it will be treated as having responded with the error`argv0: authentication not required`, whereargv0is the program name variable as set byARGBEGIN(seearg(3)).attachβ theattachfunction should check the authentication state ofafidif desired, and setr->fid->qidandofcall.qidto the qid of the file system root.attachmay be nil only if file trees are in use; in this case, the qid will be filled from the root of the tree, and no authentication will be done.walkβ if file trees are in use,walkis handled internally, andsrv->walkis never called.
if file trees are not in use, walk should consult r->ifcall.wname and r->ifcall.nwname, filling in ofcall.qid and ofcall.nqid, and also copy any necessary aux state from r->fid to r->newfid when the two are different. as long as walk sets ofcall.nqid appropriately, it can respond with a nil error string even when 9P demands an error (e.g. in the case of a short walk) β the library detects error conditions and handles them appropriately. it's got you π«Ά
and because implementing the full walk message is intricate and prone to error, the helper routine walkandclone will handle the request given pointers to two functions walk1 and (optionally) clone. clone, if non-nil, is called to signal the creation of newfid from oldfid. typically a clone routine will copy or increment a reference count in oldfid's aux element. walk1 should walk fid to name, initializing fid->qid to the new path's qid. both should return nil on success or an error message on error. walkandclone will call respond after handling the request.
walk1,cloneβ if the client provides functionssrv->walk1and (optionally)srv->clone, the 9P service loop will callwalkandclonewith these functions to handle the request. unlike thewalk1above,srv->walk1must fill in bothfid->qidand*qidwith the new qid on a successful walk.openβ if file trees are in use, the file metadata will be consulted onopen,create,remove, andwstatto see if the requester has the appropriate permissions. if not, an error will be sent back without consulting a service function.
if not using file trees, or the user has the appropriate permissions, open is called with r->ofcall.qid already initialized to the one stored in the Fid structure (that is, the one returned in the previous walk). if the qid changes, both should be updated.
createβ thecreatefunction must fill in bothr->fid->qidandr->ofcall.qidon success. when using file trees,createshould allocate a newFilewithcreatefile; note thatcreatefilemay return nil (because, say, the file already exists π¬). if thecreatefunction is nil,srvbehaves as though it were a function that always responded with the error`create prohibited`.removeβremoveshould mark the file as removed, whether by callingremovefilewhen using file trees, or by updating an internal data structure. in general it is not a good idea to clean up the aux information associated with the correspondingFileat this time, to avoid memory errors if other fids have references to that file. instead, it's suggested thatremovesimply mark the file as removed (so that further operations on it know to fail) and wait until the file tree'sdestroyfunction is called to reclaim theauxpointer. if not using file trees, it's prudent to take the analogous measures. ifremoveis not provided, all remove requests will draw`remove prohibited`errors.
...and there's more, bestie, don't go anywhere π§
π Read
Okay so read is non-negotiable β it must be provided. Itβs the function that fills r->ofcall.data with at most r->ifcall.count bytes of data from offset r->ifcall.offset of the file. It also sets r->ofcall.count to the number of bytes being returned. Slay.
If youβre using file trees, srv handles reads of directories internally and only calls read for requests on files. Cute, right?
Readstr and readbuf are your little helpers for satisfying read requests on a string or buffer. They consult the request in r->ifcall, fill r->ofcall.data, and set r->ofcall.count β but they do not call respond, so donβt expect them to send the reply for you. Similarly, dirread9p can handle directory reads in servers that arenβt using file trees. The gen function you pass gets called as necessary to fill dir with information for the nth entry in the directory. The string pointers placed in dir should be fresh copies made with estrdup9p; dirread9p will free them after each successful call to gen. Gen should return zero if it successfully filled dir, and minus one on end of directory. Periodt.
β¨ Write
write is similar but optional β it need not be provided. If it isnβt, all writes will draw write prohibited errors. If you do provide it, write should attempt to write the r->ifcall.count bytes of r->ifcall.data to offset r->ifcall.offset of the file, setting r->ofcall.count to the number of bytes actually written.
Heads up: most programs consider it an error to write less than the requested amount. π
πΈ Stat
Stat should fill r->d with the stat information for r->fid. If youβre using file trees, r->d will have been initialized with the stat info from the tree, and stat itself may be nil β yes, truly.
π¦ Wstat
wstat consults r->d when changing the metadata for r->fid, exactly as described in stat(9p). When using file trees, srv will take care to check that the request satisfies the permissions outlined in stat(9p). Otherwise, wstat should take care to enforce permissions where appropriate β no passing the buck, bestie.
π Flush
Servers that always call respond before returning from the service functions need not provide a flush implementation: flush is only necessary in programs that arrange for respond to be called asynchronously.
Flush should cause the request r->oldreq to be cancelled or hurried along. If oldreq is cancelled, this should be signalled by calling respond on oldreq with the error string interrupted. Flush must respond to r with a nil error string.
Flush may respond to r before forcing a response to r->oldreq. In this case, the library will delay sending the Rflush message until the response to r->oldreq has been sent. Order matters, okay?
Destroyfid, destroyreq, start, and end are auxiliary functions β not called in direct response to 9P requests. Theyβre the backstage crew.
π§Έ Destroyfid
When a Fidβs reference count drops to zero (i.e., it has been clunked and there are no outstanding requests referring to it), destroyfid is called to allow the program to dispose of the fid->aux pointer.
π Destroyreq
Similarly, when a Reqβs reference count drops to zero (i.e., it has been handled via respond and other outstanding pointers to it have been closed), destroyreq is called to allow the program to dispose of the r->aux pointer.
π§ Start
Before the 9P service loop begins, the service proc calls start so that the server can run any initialization that must be done from inside the service proc.
π End
Once the 9P service loop has finished β end of file reached on the service pipe, or a bad message has been read β end is called (if provided) to allow any final cleanup. For example, it was used by the Palm Pilot synchronization file system (never finished) to gracefully terminate the serial conversation once the file system had been unmounted. After calling end, the service loop (which runs in a separate process from its caller) terminates using _exits (see exits(3)).
If the chatty9p flag is at least one, a transcript of the 9P session is printed on standard error. If the chatty9p flag is greater than one, additional unspecified debugging output is generated. By convention, servers written using this library accept the -D option to increment chatty9p.
β¨ EXAMPLES β¨
/src/lib9p/ramfs.c is an example of a simple single-threaded file server. On Plan 9, see archfs, cdfs, nntpfs, webfs, and sshnet for more examples.
In general, the File interface is appropriate for maintaining arbitrary file trees (as in ramfs). The File interface is best avoided when the tree structure is easily generated as necessary; this is true when the tree is highly structured (as in cdfs and nntpfs) or is maintained elsewhere.
π SOURCE π
/src/lib9p β thatβs the source, bestie.
π SEE ALSO π
9p-fid(3), 9p-file(3), intro(9p)
Okay thatβs the whole 9P(3) page: source /src/lib9p, version/page 9P(3), and the footer date is whatever the footer says β weβre not out here inventing dates in this group chat. Go forth and clunk those fids π
β¨π¦
π¦ where this page comes from
- alpine 3.24: plan9port-doc 0_git20260208-r0 Β· LPL-1.02
- arch rolling: plan9port 20261021-2 Β· custom
- nixos 26.05: plan9port 0-unstable-2025-11-10
- nixos 26.05: plan9port-wayland 0-unstable-2025-11-26
- nixpkgs unstable: plan9port 0-unstable-2025-11-10
- nixpkgs unstable: plan9port-wayland 0-unstable-2025-11-26
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms