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

Linking

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? 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.

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.