Files
2021-02-25 01:22:17 +03:00

606 lines
15 KiB
C

/* AirScan (a.k.a. eSCL) backend for SANE
*
* Copyright (C) 2019 and up by Alexander Pevzner (pzz@apevzner.com)
* See LICENSE for license terms and conditions
*
* Event loop (runs in separate thread)
*/
#include "airscan.h"
#include <avahi-common/simple-watch.h>
#include <avahi-common/timeval.h>
#include <errno.h>
#include <unistd.h>
/******************** Constants *********************/
#define ELOOP_START_STOP_CALLBACKS_MAX 8
/******************** Static variables *********************/
static AvahiSimplePoll *eloop_poll;
static pthread_t eloop_thread;
static pthread_mutex_t eloop_mutex;
static bool eloop_thread_running;
static ll_head eloop_call_pending_list;
static bool eloop_poll_restart;
static __thread char eloop_estring[256];
static void (*eloop_start_stop_callbacks[ELOOP_START_STOP_CALLBACKS_MAX]) (bool);
static int eloop_start_stop_callbacks_count;
/******************** Standard errors *********************/
error ERROR_ENOMEM = (error) "Out of memory";
/******************** Forward declarations *********************/
static int
eloop_poll_func (struct pollfd *ufds, unsigned int nfds, int timeout, void *p);
static void
eloop_call_execute (void);
/* Initialize event loop
*/
SANE_Status
eloop_init (void)
{
pthread_mutexattr_t attr;
bool attr_initialized = false;
bool mutex_initialized = false;
SANE_Status status = SANE_STATUS_NO_MEM;
ll_init(&eloop_call_pending_list);
eloop_start_stop_callbacks_count = 0;
/* Initialize eloop_mutex */
if (pthread_mutexattr_init(&attr)) {
goto DONE;
}
attr_initialized = true;
if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) {
goto DONE;
}
if (pthread_mutex_init(&eloop_mutex, &attr)) {
goto DONE;
}
mutex_initialized = true;
/* Create AvahiSimplePoll */
eloop_poll = avahi_simple_poll_new();
if (eloop_poll == NULL) {
goto DONE;
}
avahi_simple_poll_set_func(eloop_poll, eloop_poll_func, NULL);
/* Update status */
status = SANE_STATUS_GOOD;
/* Cleanup and exit */
DONE:
if (attr_initialized) {
pthread_mutexattr_destroy(&attr);
}
if (status != SANE_STATUS_GOOD && mutex_initialized) {
pthread_mutex_destroy(&eloop_mutex);
}
return status;
}
/* Cleanup event loop
*/
void
eloop_cleanup (void)
{
if (eloop_poll != NULL) {
avahi_simple_poll_free(eloop_poll);
pthread_mutex_destroy(&eloop_mutex);
eloop_poll = NULL;
}
}
/* Add start/stop callback. This callback is called
* on a event loop thread context, once when event
* loop is started, and second time when it is stopped
*
* Start callbacks are called in the same order as
* they were added. Stop callbacks are called in a
* reverse order
*/
void
eloop_add_start_stop_callback (void (*callback) (bool start))
{
log_assert(NULL,
eloop_start_stop_callbacks_count < ELOOP_START_STOP_CALLBACKS_MAX);
eloop_start_stop_callbacks[eloop_start_stop_callbacks_count] = callback;
eloop_start_stop_callbacks_count ++;
}
/* Poll function hook
*/
static int
eloop_poll_func (struct pollfd *ufds, unsigned int nfds, int timeout,
void *userdata)
{
int rc;
(void) userdata;
eloop_poll_restart = false;
pthread_mutex_unlock(&eloop_mutex);
rc = poll(ufds, nfds, timeout);
pthread_mutex_lock(&eloop_mutex);
/* Avahi multithreading support is semi-broken. Though new
* AvahiWatch could be added from a context of any thread
* (Avahi internal structures are properly interlocked, and
* event loop thread is properly woken by poll->watch_new()),
* Avahi internal indices are only rebuild in the beginning
* of the avahi_simple_poll_iterate(), when avahi_simple_poll_prepare()
* is called. So when Avahi returns from the poll() syscall
* and calls avahi_simple_poll_dispatch(), it asserts, if new
* AvahiWatch, which causes process to crash.
*
* To work around this crash, we force avahi_simple_poll_iterate()
* to exit before calling of avahi_simple_poll_dispatch() by
* returning error code from here. The subsequent call of
* avahi_simple_poll_iterate() fixes the situation by calling
* avahi_simple_poll_prepare() first.
*
* The returned error code cannot be EINTR, because interrupted
* system call errors are ignored by Avahi (it simply restarts
* the operation) and needs to be distinguished by a "normal"
* errors, so event loop in the eloop_thread_func() will handle
* it in appropriate manner.
*
* For now we use EBUSY error code for this purpose
*/
if (eloop_poll_restart) {
/* We have to return an error other than EINTR to restart the
avahi loop. */
errno = EBUSY;
return -1;
}
return rc;
}
/* Event loop thread main function
*/
static void*
eloop_thread_func (void *data)
{
int i;
(void) data;
pthread_mutex_lock(&eloop_mutex);
for (i = 0; i < eloop_start_stop_callbacks_count; i ++) {
eloop_start_stop_callbacks[i](true);
}
__atomic_store_n(&eloop_thread_running, true, __ATOMIC_SEQ_CST);
do {
eloop_call_execute();
i = avahi_simple_poll_iterate(eloop_poll, -1);
} while (i == 0 || (i < 0 && (errno == EINTR || errno == EBUSY)));
for (i = eloop_start_stop_callbacks_count - 1; i >= 0; i --) {
eloop_start_stop_callbacks[i](false);
}
pthread_mutex_unlock(&eloop_mutex);
return NULL;
}
/* Start event loop thread.
*/
void
eloop_thread_start (void)
{
int rc;
useconds_t usec = 100;
rc = pthread_create(&eloop_thread, NULL, eloop_thread_func, NULL);
if (rc != 0) {
log_panic(NULL, "pthread_create: %s", strerror(rc));
}
/* Wait until thread is started and all start callbacks are executed */
while (!__atomic_load_n(&eloop_thread_running, __ATOMIC_SEQ_CST)) {
usleep(usec);
usec += usec;
}
}
/* Stop event loop thread and wait until its termination
*/
void
eloop_thread_stop (void)
{
if (__atomic_load_n(&eloop_thread_running, __ATOMIC_SEQ_CST)) {
avahi_simple_poll_quit(eloop_poll);
pthread_join(eloop_thread, NULL);
__atomic_store_n(&eloop_thread_running, false, __ATOMIC_SEQ_CST);
}
}
/* Acquire event loop mutex
*/
void
eloop_mutex_lock (void)
{
pthread_mutex_lock(&eloop_mutex);
}
/* Release event loop mutex
*/
void
eloop_mutex_unlock (void)
{
pthread_mutex_unlock(&eloop_mutex);
}
/* Wait on conditional variable under the event loop mutex
*/
void
eloop_cond_wait (pthread_cond_t *cond)
{
pthread_cond_wait(cond, &eloop_mutex);
}
/* Get AvahiPoll that runs in event loop thread
*/
const AvahiPoll*
eloop_poll_get (void)
{
return avahi_simple_poll_get(eloop_poll);
}
/* eloop_call_pending represents a pending eloop_call
*/
typedef struct {
void (*func)(void*); /* Function to be called */
void *data; /* It's argument */
uint64_t callid; /* For eloop_call_cancel() */
ll_node node; /* In eloop_call_pending_list */
} eloop_call_pending;
/* Execute function calls deferred by eloop_call()
*/
static void
eloop_call_execute (void)
{
ll_node *node;
while ((node = ll_pop_beg(&eloop_call_pending_list)) != NULL) {
eloop_call_pending *pending;
pending = OUTER_STRUCT(node, eloop_call_pending, node);
pending->func(pending->data);
mem_free(pending);
}
}
/* Call function on a context of event loop thread
* The returned value can be supplied as a `callid'
* parameter for the eloop_call_cancel() function
*/
uint64_t
eloop_call (void (*func)(void*), void *data)
{
eloop_call_pending *p = mem_new(eloop_call_pending, 1);
static uint64_t callid;
uint64_t ret;
p->func = func;
p->data = data;
pthread_mutex_lock(&eloop_mutex);
ret = ++ callid;
p->callid = ret;
ll_push_end(&eloop_call_pending_list, &p->node);
pthread_mutex_unlock(&eloop_mutex);
avahi_simple_poll_wakeup(eloop_poll);
return ret;
}
/* Cancel pending eloop_call
*
* This is safe to cancel already finished call (at this
* case nothing will happen)
*/
void
eloop_call_cancel (uint64_t callid)
{
ll_node *node;
for (LL_FOR_EACH(node, &eloop_call_pending_list)) {
eloop_call_pending *p = OUTER_STRUCT(node, eloop_call_pending, node);
if (p->callid == callid) {
ll_del(&p->node);
mem_free(p);
return;
}
}
}
/* Event notifier. Calls user-defined function on a context
* of event loop thread, when event is triggered. This is
* safe to trigger the event from a context of any thread
* or even from a signal handler
*/
struct eloop_event {
pollable *p; /* Underlying pollable event */
eloop_fdpoll *fdpoll; /* Underlying fdpoll */
void (*callback)(void*); /* user-defined callback */
void *data; /* callback's argument */
};
/* eloop_event eloop_fdpoll callback
*/
static void
eloop_event_callback (int fd, void *data, ELOOP_FDPOLL_MASK mask)
{
eloop_event *event = data;
(void) fd;
(void) mask;
pollable_reset(event->p);
event->callback(event->data);
}
/* Create new event notifier. May return NULL
*/
eloop_event*
eloop_event_new (void (*callback)(void *), void *data)
{
eloop_event *event;
pollable *p;
p = pollable_new();
if (p == NULL) {
return NULL;
}
event = mem_new(eloop_event, 1);
event->p = p;
event->callback = callback;
event->data = data;
event->fdpoll = eloop_fdpoll_new(pollable_get_fd(p),
eloop_event_callback, event);
eloop_fdpoll_set_mask(event->fdpoll, ELOOP_FDPOLL_READ);
return event;
}
/* Destroy event notifier
*/
void
eloop_event_free (eloop_event *event)
{
eloop_fdpoll_free(event->fdpoll);
pollable_free(event->p);
mem_free(event);
}
/* Trigger an event
*/
void
eloop_event_trigger (eloop_event *event)
{
pollable_signal(event->p);
}
/* Timer. Calls user-defined function after a specified
* interval
*/
struct eloop_timer {
AvahiTimeout *timeout; /* Underlying AvahiTimeout */
void (*callback)(void *); /* User callback */
void *data; /* User data */
};
/* eloop_timer callback for AvahiTimeout
*/
static void
eloop_timer_callback (AvahiTimeout *t, void *data)
{
eloop_timer *timer = data;
(void) t;
timer->callback(timer->data);
eloop_timer_cancel(timer);
}
/* Create new timer. Timeout is in milliseconds
*/
eloop_timer*
eloop_timer_new (int timeout, void (*callback)(void *), void *data)
{
const AvahiPoll *poll = eloop_poll_get();
eloop_timer *timer = mem_new(eloop_timer, 1);
struct timeval end;
avahi_elapse_time(&end, timeout, 0);
timer->timeout = poll->timeout_new(poll, &end, eloop_timer_callback, timer);
timer->callback = callback;
timer->data = data;
return timer;
}
/* Cancel a timer
*
* Caller SHOULD NOT cancel expired timer (timer with called
* callback) -- this is done automatically
*/
void
eloop_timer_cancel (eloop_timer *timer)
{
const AvahiPoll *poll = eloop_poll_get();
poll->timeout_free(timer->timeout);
mem_free(timer);
}
/* Convert ELOOP_FDPOLL_MASK to string. Used for logging.
*/
const char*
eloop_fdpoll_mask_str (ELOOP_FDPOLL_MASK mask)
{
switch (mask & ELOOP_FDPOLL_BOTH) {
case 0: return "{--}";
case ELOOP_FDPOLL_READ: return "{r-}";
case ELOOP_FDPOLL_WRITE: return "{-w}";
case ELOOP_FDPOLL_BOTH: return "{rw}";
}
return "{??}"; /* Should never happen indeed */
}
/* eloop_fdpoll notifies user when file becomes
* readable, writable or both, depending on its
* event mask
*/
struct eloop_fdpoll {
AvahiWatch *watch; /* Underlying AvahiWatch */
int fd; /* Underlying file descriptor */
ELOOP_FDPOLL_MASK mask; /* Mask of active events */
void (*callback)( /* User-defined callback */
int, void*, ELOOP_FDPOLL_MASK);
void *data; /* Callback's data */
};
/* eloop_fdpoll callback for AvahiWatch
*/
static void
eloop_fdpoll_callback (AvahiWatch *w, int fd, AvahiWatchEvent event,
void *data)
{
eloop_fdpoll *fdpoll = data;
ELOOP_FDPOLL_MASK mask = 0;
(void) w;
if ((event & AVAHI_WATCH_IN) != 0) {
mask |= ELOOP_FDPOLL_READ;
}
if ((event & AVAHI_WATCH_OUT) != 0) {
mask |= ELOOP_FDPOLL_WRITE;
}
fdpoll->callback(fd, fdpoll->data, mask);
}
/* Create eloop_fdpoll
*
* Callback will be called, when file will be ready for read/write/both,
* depending on mask
*
* Initial mask value is 0, and it can be changed, using
* eloop_fdpoll_set_mask() function
*/
eloop_fdpoll*
eloop_fdpoll_new (int fd,
void (*callback) (int, void*, ELOOP_FDPOLL_MASK), void *data)
{
const AvahiPoll *poll = eloop_poll_get();
eloop_fdpoll *fdpoll = mem_new(eloop_fdpoll, 1);
fdpoll->fd = fd;
fdpoll->callback = callback;
fdpoll->data = data;
eloop_poll_restart = true;
fdpoll->watch = poll->watch_new(poll, fd, 0, eloop_fdpoll_callback, fdpoll);
return fdpoll;
}
/* Destroy eloop_fdpoll
*/
void
eloop_fdpoll_free (eloop_fdpoll *fdpoll)
{
const AvahiPoll *poll = eloop_poll_get();
poll->watch_free(fdpoll->watch);
mem_free(fdpoll);
}
/* Set eloop_fdpoll event mask. It returns a previous value of event mask
*/
ELOOP_FDPOLL_MASK
eloop_fdpoll_set_mask (eloop_fdpoll *fdpoll, ELOOP_FDPOLL_MASK mask)
{
ELOOP_FDPOLL_MASK old_mask = fdpoll->mask;
if (old_mask != mask) {
const AvahiPoll *poll = eloop_poll_get();
AvahiWatchEvent events = 0;
if ((mask & ELOOP_FDPOLL_READ) != 0) {
events |= AVAHI_WATCH_IN;
}
if ((mask & ELOOP_FDPOLL_WRITE) != 0) {
events |= AVAHI_WATCH_OUT;
}
fdpoll->mask = mask;
poll->watch_update(fdpoll->watch, events);
}
return old_mask;
}
/* Format error string, as printf() does and save result
* in the memory, owned by the event loop
*
* Caller should not free returned string. This is safe
* to use the returned string as an argument to the
* subsequent eloop_eprintf() call.
*
* The returned string remains valid until next call
* to eloop_eprintf(), which makes it usable to
* report errors up by the stack. However, it should
* not be assumed, that the string will remain valid
* on a next eloop roll, so don't save this string
* anywhere, if you need to do so, create a copy!
*/
error
eloop_eprintf(const char *fmt, ...)
{
va_list ap;
char buf[sizeof(eloop_estring)];
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
strcpy(eloop_estring, buf);
va_end(ap);
return ERROR(eloop_estring);
}
/* vim:ts=8:sw=4:et
*/