pipe-tools
Software
www.skarnet.org
The nptrigger library interface
The nptrigger library has been designed to implement portable
checkpoints.
Definitions
A fifodir is a path to a directory used exclusively by nptrigger,
containing only named pipes of a specific format.
An event is an array of NPT_MSGSIZE characters, usually 4, which
allows for 2^32 separate events per fifodir.
A checkpoint is a place where programs - the listeners -
will pause, waiting for some events to occur - triggered by other processes,
the notifiers.
nptrigger implements checkpoints with fifodirs.
Security model
Every process that waits on a fifodir must have the same gid. If you want
to re-use an empty fifodir with processes that have a different gid from
the previous fifodir creator, you must unlink the fifodir first.
The notifiers must have the same gid as the listeners.
A program can cause trouble to a listener only if it has the same uid. If
you have more than one listener, you should give all of them different uids,
if possible.
Compiling
- Add /package/admin/pipe-tools/include and /package/prog/skalibs/include
to your header directory list
- Use #include "nptrigger.h"
Linking
- Define a global variable PROG of type const char *
that contains the name of your executable.
- Add /package/admin/pipe-tools/library and
/package/prog/skalibs/include to your library directory list
- If you used the npt_listen function, that tries to connect to
a nptlistend service: link with libnptrigger.a, libwebipc.a,
libstddjb.a, and add `cat /package/prog/skalibs/sysdeps/socket.lib`
to your linker command line.
- Else link with libnptrigger.a, libstddjb.a.
You should normally use the npt_listenf function instead, so you
probably won't need libwebipc.a nor socket.lib.
Programming: high-level interfaces
Notifying
typedef char npt_msg_t[NPT_MSGSIZE] ;
int r ;
char *path ;
npt_msg_t event ;
char *eventstring ;
r = nptrigger_notify(path, event) ;
r = nptrigger_notifys(path, eventstring) ;
nptrigger_notify notifies the fifodir located at path
with event. Every listener waiting on path will be notified.
nptrigger_notify returns the number of listeners that have
been successfully notified, or -1 if an error occurs.
nptrigger_notifys is similar, except it constructs event
from a simple string, which makes a nicer interface.
Synchronously listening
int r ;
char *path ;
npt_msg_t event ;
npt_msg_t *events ;
unsigned int n ;
unsigned int timeout ;
r = nptrigger_wait(path, event, timeout) ;
r = nptrigger_waitfor(path, event, timeout) ;
r = nptrigger_wfo(path, events, n, timeout) ;
r = nptrigger_wfa(path, events, n, timeout) ;
nptrigger_waitforone and nptrigger_waitforall are
aliases for nptrigger_wfo and nptrigger_wfa, respectively.
nptrigger_waitfor() is an alias for nptrigger_wfa when
you have only one event to wait for.
nptrigger_wfa() and nptrigger_wfo()
create a checkpoint at path, and wait there.
events must be an array of at least n events.
If an error occurs, the functions return -1.
If timeout seconds elapse without them getting the events they want, they return n.
When nptrigger_wfa gets all of the events listed in events, it returns a number between 0 and n-1.
When nptrigger_wfo gets one of the events listed in events, it
returns its position in the array, starting at 0. If one of events' elements is composed
of only null characters, it matches any event, and the array element is overwritten by the actual event it matches.
nptrigger_wait() waits for the first event at path,
and writes it in event if it doesn't time out or get interrupted.
Asynchronously listening
Rationale
You're a single-threaded process. You want to trigger an action on your
system, such as sending a TERM signal to a process, and wait for the
action to complete, such as be notified when the process actually dies.
What should you do?
- call nptrigger_waitfor() first? Nope, it will block.
- Trigger the action, like kill(), then call
nptrigger_waitfor(), which will block until the action completes?
Yeah, sure. Can you see the major race condition there? If the action
completes between the moment you trigger it and the moment
nptrigger_waitfor() is actually listening, you will miss the
notification, and may block forever. Oops.
What you need is a way to be certain that you're listening before
you trigger the action. Even if you're multi-threaded, you cannot know for
sure when the thread that calls the listening function will actually be
listening. That is why some asynchronous functions are provided.
Setting up the listener
typedef struct nptinfo nptinfo, *nptinfo_ref ;
int r ;
nptinfo a ;
char *path ;
npt_msg_t *events ;
unsigned int n ;
unsigned int timeout ;
char type ;
r = nptrigger_listenf(path, events, n, timeout, type, &a) ;
r = nptrigger_listen(path, events, n, timeout, type, &a) ;
nptrigger_listenf() and npt_listen() both set
up a listener for n events on path, with
a timeout of timeout seconds. It stores its state in a
magic structure a.
type must be either NPT_TYPE_AND or
NPT_TYPE_OR, to specify whether you are waiting for
all of the n events or only one of them.
The function returns -1 on error, or 0 on success. When it
successfully returns, it guarantees that the listener is set up
so that you won't miss any notification on path.
The nptlistend program must be found in the caller's PATH.
The difference between nptrigger_listen() and
nptrigger_listenf() is their operation mode.
nptrigger_listenf
spawns a child nptlistend process to perform the
listening, whereas nptrigger_listen tries to
connect to a nptlistend service.
Which one you should use is up to you; nptrigger_listenf
should be faster and less error-prone - it does not require any daemon to
be running on the machine. nptrigger_listen has been provided for
programs that have special SIGCHILD handlers, when you cannot safely fork().
Waiting for a notification and cleaning up
int r ;
int fd ;
nptinfo a ;
npt_msg_t answer ;
fd = nptrigger_fd(&a) ;
r = nptrigger_answer(&a, answer) ;
nptrigger_close(&a) ;
a is the magic structure used with the nptrigger_listen
call.
nptrigger_answer() blocks until it is notified; then it
stores in answer the event it received (which is only meaningful
for a NPT_TYPE_OR query type) and returns 0. It returns -1 on error.
Alternatively, you can use nptrigger_fd() to get a file
descriptor to be used in a poll() or select() call.
Never read directly on the fd though; always use nptrigger_answer()
to get the message.
You should call nptrigger_close() after getting the answer.
Actually, you can call nptrigger_close() at any time; it will
discard the corresponding listener.
Programming: low-level interfaces
You really should not have to use those functions directly; they are not
defined in the nptrigger.h header anyway. This is for reference only.
Listening
int r ;
const char *path ;
npt_msg_t *events ;
unsigned int n ;
unsigned int timeout ;
char type ;
int fdsync ;
int fdcontrol ;
r = nptrigger_block(path, events, n, timeout, type, fdsync, fdcontrol) ;
path, events, n, and timeout are
the same arguments you give to nptrigger_wfa or
nptrigger_wfo. type is either NPT_TYPE_AND or
NPT_TYPE_OR. fdsync must be open for writing;
fdcontrol must be open for reading.
nptrigger_block() listens for notifications on path.
- When it has started listening, it writes a null byte on fdsync.
- If it encounters an error, it returns -1.
- If it reads anything on fdcontrol, or fdcontrol
closes, it returns -2.
- If it times out, it exits n.
- If it gets the notifications it has been asked, it writes the answer
(a npt_msg_t) on fdsync and exits its position in events, starting
at 0. The answer is only meaningful with a NPT_TYPE_OR request type.
Communicating with a nptlistend process
int r ;
char *path ;
npt_msg_t *events ;
unsigned int n ;
unsigned int timeout ;
char type ;
int fdsync ;
int fdcontrol ;
r = nptrigger_sendinfo(path, events, n, timeout, type, fdsync, fdcontrol) ;
If you have a newborn nptlistend process, and have
fdcontrol writing to its stdin and fdsync reading
from its stdout, then calling nptrigger_sendinfo() will
make it listen on what you want.
nptrigger_sendinfo returns -1 on error and 0 on success.
If it returns 0, you have the guarantee that the nptlistend
process is really listening.