martes, 16 de agosto de 2011

Libninep

When this project was started one of the first questions that had to be solved was how to serve the 9P protocol from unix. We already depend on some Inferno libraries and Inferno includes a library to write 9P servers, libstyx. This library looks like an obvious choice. However, libstyx has some limitations: it does not support out-of-order replies, does not give the option to select on extra fds (we need that for the connection with X) and its representation of file trees is somewhat static.

Libninep is a modified libstyx, which tries to solve these issues.

Most of the content of styx(10) and styxserver(10) still applies to libninep (after s/styx/ninep/g, of course). The most important differences are explained in the next paragraphs.

Main loop
The loop of a libstyx application has this form:
for(;;)
  styxwait(&s);
  styxproccess(&s);
}
After styxwait returns, styxproccess reads the next request, looks for the corresponding function in s->ops, runs it if found, and replies. With libninep we have more options. The main loop of devwsys looks like this:
for(;;)
  nineplisten(&s, xfd);
  ninepwait(&s);
  if(ninepready(&s, xfd)
    xevent();
  ninepdefault(&s);
  updaterefs();
  ninepreply(&s);
}
There are some new library functions here: nineplisten adds a fd to the fd set to select from and ninepready checks if it is ready. The styxprocess function has been split in two: ninepdefault and ninepreply. However, the key difference is ninepwait, which is doing more than styxwait.

While styxwait just waits for a fd to be ready and let styxprocess to do all the work, ninepwait will read the next 9P request when possible, setting s->curc and s->fcall. ninepdefault will process this request, using s->ops when available, and probably turning a Tmessage into a Rmessage. Only when ninepreply is called this request is actually replied. This way, we can use ninepdefault for common operations but, if that is not enough, we can also process 9P requests on our own, just filling the fields of s->fcall and calling ninepreply when we are done.

What devwsys is doing is calling updaterefs after ninepdefault. This function will take care of updating the number of references to a window after an attach, a walk, or a clunk transaction. To do this with libstyx, we would have to give up on using its facilities to handle file trees and implement walks and dirreads ourselves.

Out of order replies
When reading event files (cons, mouse) the file server cannot reply immediately to a request, it has to wait until an event is received. This functionality is provided by two functions: ninepreplylater will put a request on hold and returns a pointer to a Pending struct, which can be passed to ninepcompleted to reply when we are done. After calling ninepreplylater, ninepreply will have no effect until ninepwait is called again.

It is the responsibility of the client to keep a queue of pending requests if desired. However, libninep will take care of marking them as flushed if a Tflush request is made.

File trees: binding
Libstyx includes some functions to help with the creation of file trees: styxadddir and styxaddfile. The advantage of using these functions is that using them we do not have to worry about implementing walks or directory reads, but they do not allow us to give several entry points to a given file. For example, devwsys has to give access to the files of a window in the wsys directory of every other window and on its root, and the clients of draw should be accessible by every window.

This problem is solved in Linux using links. The same problem is solved by Plan 9 (and Inferno) in a more elegant way by bind. So, libninep includes a ninepbind function. Its usage is quite simple: given the qid of two directories (pqid and qid) the files of qid will also be accessible from pqid.

In devwsys, the file system root only includes two directories: /wsys and /draw:
ninepadddir(s, Qroot, Qwsys, "wsys", 0555, eve);
ninepadddir(s, Qroot, Qdraw, "draw", 0555, eve);
When a new window is created (due to an attach request), files for that window are created inside the corresponding /wsys/n/ directory:
p = PATH(w->id, 0);
ninepadddir(s, Qwsys, p, name, 0555, eve);
ninepaddfile(s, p, p|Qcons, "cons", 0666, eve);
ninepaddfile(s, p, p|Qconsctl, "consctl", 0222, eve);
...
ninepadddir(s, p, p|Qwsys, "wsys", 0555, eve);
ninepadddir(s, p, p|Qdraw, "draw", 0555, eve);
ninepaddfile(s, p|Qdraw, p|Qnew, "new", 0666, eve);
Then, we bind the directories /wsys and /draw to the corresponding directories of the window:
ninepbind(s, p|Qwsys, Qwsys);
ninepbind(s, p|Qdraw, Qdraw); 
When a new client attachs to devwsys, wsysattach (which is server->ops->attach) will be called. This functions sets the qid to be returned to be the one corresponding to /wsys/n and the binding magic will take care automatically of walking and reading directories.
Note 1: rio's fs lets you to walk to /wsys/n/wsys, but no further. Devwsys, which uses libninep and bind directories, does not have this limit. You can walk as deep as you want into /wsys/n1/wsys/n2/wsys/n1/wsys/...
Note 2: The files of every draw client are added to /draw/n. Although /draw is bound to /wsys/n/draw we still have access to the /wsys/n/draw/new file which corresponds to the window.
Announce on unix addresses
You can only post your 9P server to a port using libstyx. Instead, libninep can also listen on unix!... addresses, as libixp does. A new function, ninepnamespace, gives the default namespace to use, maybe read from the environment variable NAMESPACE, else of the form /tmp/ns.$USER.$DISPLAY/ (as p9p and libixp do).

No hay comentarios:

Publicar un comentario