s6: the ftrigr library interface
Keywords: s6 ftrig notification subscriber listener libftrigr ftrigr library interface

libs6
s6
Software
skarnet.org

# The `ftrigr` library interface

The `ftrigr` library provides an API for listeners, i.e. programs that want to subscribe to fifodirs and be instantly notified when the proper sequence of events happens.

## Programming

Check the `s6/ftrigr.h` header for the exact function prototypes.

Make sure your application is not disturbed by children it doesn't know it has. This means paying some attention to the SIGCHLD handler, if any, and to the way you perform `waitpid()`s. The best practice is to use a self-pipe to handle SIGCHLD (as well as other signals the application needs to trap), and to always use `wait_nohang()` to reap children, simply ignoring pids you don't know.

### A programming example

The `src/pipe-tools/s6-ftrig-listen1.c` and `src/supervision/s6-svwait.c` files in the s6 package, for instance, illustrate how to use the ftrigr library.

### Synchronous functions with a specified maximum execution time

- Synchronous functions take a `tain const *` (deadline) parameter and a `tain *` (stamp) parameter. Those are pointers to tain structures containing absolute times; the former represents a deadline (in most cases, this time will be in the future) and the latter must be an accurate enough timestamp. These structures can be filled using the `tain_` primitives declared in skalibs/tai.h.
- ("Accurate enough" means that no blocking system call must have been made since the last time stamp was updated (by `tain_now(&stamp)`). It's a good policy to always update stamp right after a (potentially) blocking system call like `select()` returns. And unless the application is extremely CPU-intensive (think calculus for physicists or astronomers) updating stamp more frequently is unnecessary.)
- If such a synchronous function still hasn't returned when the deadline occurs, then it will immediately return a failure code and set errno to ETIMEDOUT. It is possible to pass null pointers to the function instead of pointers to tain structures, in which case the function will never timeout.
- If a timeout occurs, the library does not guarantee proper interprocess communication later on; the application should either die, or at least close the communication channel and open a new one.
- If any waiting occurred, the stamp structure is automatically updated by the called function, so it always represents an accurate enough estimation of the current time. This allows the programmer to call several such functions in a sequence without modifying the deadline and stamp parameters: then the whole sequence is bound in execution time.
- This is a general safety mechanism implemented in libunixonacid: in interprocess communication, purely synchronous primitives are dangerous because they make the calling process rely on proper behaviour of the called process. Giving synchronous primitives the ability to timeout allows developers to write reliable programs even when interacting with software they have no control on.

### Starting and ending a session

    ftrigr a = FTRIGR_ZERO ;
    tain deadline, stamp ;

    tain_now(&stamp) ;
    tain_addsec(&deadline, &stamp, 2)

    ftrigr_startf(&a, &deadline, &stamp) ;

`ftrigr_startf()` starts a session with an ftrigrd process as a child (which is the simplest usage).
`a` is an ftrigr structure that can be declared in the stack and must be initialized to FTRIGR_ZERO. `stamp` must be an accurate enough timestamp.
If the session initialization fails, the function returns 0 and errno is set; else the function returns 1.

If the absolute time `deadline` is reached and the function has not returned yet, it immediately returns 0 with errno set to ETIMEDOUT. Only local interprocess communications are involved; unless your system is heavily overloaded, the function should return near-instantly. One or two seconds of delay between `stamp` and `deadline` should be enough: if the function takes more than that to return, then there is a problem with the underlying processes.

You can have more than one session open in parallel, by declaring several distinct `ftrigr` structures and calling `ftrigr_startf()` more than once. However, this is useless, since one single session can handle virtually as many concurrent fifodirs as your application needs.

    ftrigr_end(&a) ;

`ftrigr_end()` frees all the resources used by the session. The `a` structure is then reusable for another session.

### Subscribing to a fifodir

    char const *path = "/var/lib/myservice/fifodir" ;
    char const *re = "a.*b|c*d" ;
    uint32_t options = 0 ;
    uint32_t timeout = 60000 ;
    uint32_t id ;

    int r = ftrigr_subscribe(&a, &id, options, timeout, path, re, &deadline, &stamp) ;

`ftrigr_subscribe()` instructs the s6-ftrigrd daemon, related to the open session represented by the `a` structure, to subscribe to the `path` fifodir, and to notify the application when it receives a series of events that matches the `re` regexp.

`options` can be 0 or FTRIGR_REPEAT. If it is 0, the daemon will automatically unsubscribe from `path` once `re` has been matched by a series of events. If it is FTRIGR_REPEAT, it will remain subscribed until told otherwise.

If `timeout` is nonzero, it represents a number of milliseconds; after this delay, the daemon will automatically unsubscribe from `path` and report an ETIMEDOUT error to the client. It is not advised to use a nonzero timeout along with the FTRIGR_REPEAT option.

If it fails, `ftrigr_subscribe()` returns 0 and sets errno. If it succeeds, it returns 1 and stores a number identifying the subscription into `id`.

`ftrigr_subscribe()` should return near-instantly, but if deadline is reached, it will return 0 ETIMEDOUT. If `ftrigr_subscribe()` returns successfully, then the s6-ftrigrd daemon is guaranteed to be listening on `path`, and events can be sent without the risk of a race condition.

### Synchronously waiting for events

    uint32_t list[1] = { id } ;
    unsigned int n = 1 ;
    ftrigr_string fs ;

    // r = ftrigr_wait_and(&a, list, n, &deadline, &stamp) ;
    r = ftrigr_wait_or(&a, list, n, &deadline, &stamp, &fs) ;

`ftrigr_wait_and()` waits for all the `n` fifodirs whose ids are listed in `list` to receive an event. It returns -1 in case of error or timeout, or a non-negative integer in case of success.

`ftrigr_wait_or()` waits for one of the `n` fifodirs whose ids are listed in `list` to receive an event. It returns -1 in case of error or timeout; if it succeeds, the return value is the position in `list`, starting at 0, of the identifier that received an event; and `fs` is the list of events that were received since the subscription and matched the `re` regular expression. `fs.s` is a `char *` pointing to the (not null-terminated) string of events, and `fs.len` is its length.

### Asynchronously waiting for events

(from now on, the functions are listed with their prototypes instead of usage examples.)

    int ftrigr_fd (ftrigr const *a)

Returns a file descriptor to select on for reading. Do not `read()` it though.

    int ftrigr_update (ftrigr *a)

Call this function whenever the fd checks readability: it will update a's internal structures with information from the s6-ftrigrd daemon. It returns -1 (and sets errno) if an error occurs, 0 if there were no events, and 1 if events were received.

    int ftrigr_peek (ftrigr *a, uint32_t id, ftrigr_string *fs)

Checks whether an event happened to id. Use after a call to `ftrigr_update()`.

- If an error occurred, returns -1 and sets errno. The error number may have been transmitted from s6-ftrigrd.
- If no notification happened yet, returns 0.
- If something happened, returns 1, and `fs` contains the string of events that were received since the subscription or since the last call to `ftrigr_ack()` (see below). `fs->s` is a pointer to the non-null-terminated string, and `fs->len` is its length.

    void ftrigr_ack (ftrigr *a, uint32_t id)

Resets the stored string of events. The next invocation of `ftrigr_peek()` will only show new events, if any.

    int ftrigr_release (ftrigr *a, uint32_t id)

Frees the resources used by subscription `id`. Use this after getting an event from a subscription done without the FTRIGR_REPEAT flag and reading its results, if you want to keep the session open and perform more subscriptions.

If subscription `id` was given the FTRIGR_REPEAT flag, use `ftrigr_unsubscribe()` instead. If you're not going to perform other subscriptions, `ftrigr_end()` will free all the resources without you needing to call `ftrigr_release()` first.