mirror of
https://github.com/openharmony/third_party_sane-airscan.git
synced 2026-06-30 21:17:55 -04:00
606 lines
15 KiB
C
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
|
|
*/
|