1 /* AirScan (a.k.a. eSCL) backend for SANE
2  *
3  * Copyright (C) 2019 and up by Alexander Pevzner (pzz@apevzner.com)
4  * See LICENSE for license terms and conditions
5  *
6  * Event loop (runs in separate thread)
7  */
8 
9 #include "airscan.h"
10 
11 #include <avahi-common/simple-watch.h>
12 #include <avahi-common/timeval.h>
13 
14 #include <errno.h>
15 #include <unistd.h>
16 
17 /******************** Constants *********************/
18 #define ELOOP_START_STOP_CALLBACKS_MAX  8
19 
20 /******************** Static variables *********************/
21 static AvahiSimplePoll *eloop_poll;
22 static pthread_t eloop_thread;
23 static pthread_mutex_t eloop_mutex;
24 static bool eloop_thread_running;
25 static ll_head eloop_call_pending_list;
26 static bool eloop_poll_restart;
27 
28 static __thread char eloop_estring[256];
29 static void (*eloop_start_stop_callbacks[ELOOP_START_STOP_CALLBACKS_MAX]) (bool);
30 static int eloop_start_stop_callbacks_count;
31 
32 /******************** Standard errors *********************/
33 error ERROR_ENOMEM = (error) "Out of memory";
34 
35 /******************** Forward declarations *********************/
36 static int
37 eloop_poll_func (struct pollfd *ufds, unsigned int nfds, int timeout, void *p);
38 
39 static void
40 eloop_call_execute (void);
41 
42 /* Initialize event loop
43  */
44 SANE_Status
eloop_init(void)45 eloop_init (void)
46 {
47     pthread_mutexattr_t attr;
48     bool                attr_initialized = false;
49     bool                mutex_initialized = false;
50     SANE_Status         status = SANE_STATUS_NO_MEM;
51 
52     ll_init(&eloop_call_pending_list);
53     eloop_start_stop_callbacks_count = 0;
54 
55     /* Initialize eloop_mutex */
56     if (pthread_mutexattr_init(&attr)) {
57         goto DONE;
58     }
59 
60     attr_initialized = true;
61     if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) {
62         goto DONE;
63     }
64 
65     if (pthread_mutex_init(&eloop_mutex, &attr)) {
66         goto DONE;
67     }
68 
69     mutex_initialized = true;
70 
71     /* Create AvahiSimplePoll */
72     eloop_poll = avahi_simple_poll_new();
73     if (eloop_poll == NULL) {
74         goto DONE;
75     }
76 
77     avahi_simple_poll_set_func(eloop_poll, eloop_poll_func, NULL);
78 
79     /* Update status */
80     status = SANE_STATUS_GOOD;
81 
82     /* Cleanup and exit */
83 DONE:
84     if (attr_initialized) {
85         pthread_mutexattr_destroy(&attr);
86     }
87 
88     if (status != SANE_STATUS_GOOD && mutex_initialized) {
89         pthread_mutex_destroy(&eloop_mutex);
90     }
91 
92     return status;
93 }
94 
95 /* Cleanup event loop
96  */
97 void
eloop_cleanup(void)98 eloop_cleanup (void)
99 {
100     if (eloop_poll != NULL) {
101         avahi_simple_poll_free(eloop_poll);
102         pthread_mutex_destroy(&eloop_mutex);
103         eloop_poll = NULL;
104     }
105 }
106 
107 /* Add start/stop callback. This callback is called
108  * on a event loop thread context, once when event
109  * loop is started, and second time when it is stopped
110  *
111  * Start callbacks are called in the same order as
112  * they were added. Stop callbacks are called in a
113  * reverse order
114  */
115 void
eloop_add_start_stop_callback(void (* callback)(bool start))116 eloop_add_start_stop_callback (void (*callback) (bool start))
117 {
118     log_assert(NULL,
119             eloop_start_stop_callbacks_count < ELOOP_START_STOP_CALLBACKS_MAX);
120 
121     eloop_start_stop_callbacks[eloop_start_stop_callbacks_count] = callback;
122     eloop_start_stop_callbacks_count ++;
123 }
124 
125 /* Poll function hook
126  */
127 static int
eloop_poll_func(struct pollfd * ufds,unsigned int nfds,int timeout,void * userdata)128 eloop_poll_func (struct pollfd *ufds, unsigned int nfds, int timeout,
129         void *userdata)
130 {
131     int     rc;
132 
133     (void) userdata;
134 
135     eloop_poll_restart = false;
136 
137     pthread_mutex_unlock(&eloop_mutex);
138     rc = poll(ufds, nfds, timeout);
139     pthread_mutex_lock(&eloop_mutex);
140 
141     /* Avahi multithreading support is semi-broken. Though new
142      * AvahiWatch could be added from a context of any thread
143      * (Avahi internal structures are properly interlocked, and
144      * event loop thread is properly woken by poll->watch_new()),
145      * Avahi internal indices are only rebuild in the beginning
146      * of the avahi_simple_poll_iterate(), when avahi_simple_poll_prepare()
147      * is called. So when Avahi returns from the poll() syscall
148      * and calls avahi_simple_poll_dispatch(), it asserts, if new
149      * AvahiWatch, which causes process to crash.
150      *
151      * To work around this crash, we force avahi_simple_poll_iterate()
152      * to exit before calling of avahi_simple_poll_dispatch() by
153      * returning error code from here. The subsequent call of
154      * avahi_simple_poll_iterate() fixes the situation by calling
155      * avahi_simple_poll_prepare() first.
156      *
157      * The returned error code cannot be EINTR, because interrupted
158      * system call errors are ignored by Avahi (it simply restarts
159      * the operation) and needs to be distinguished by a "normal"
160      * errors, so event loop in the eloop_thread_func() will handle
161      * it in appropriate manner.
162      *
163      * For now we use EBUSY error code for this purpose
164      */
165     if (eloop_poll_restart) {
166         /* We have to return an error other than EINTR to restart the
167            avahi loop. */
168         errno = EBUSY;
169         return -1;
170     }
171 
172     return rc;
173 }
174 
175 /* Event loop thread main function
176  */
177 static void*
eloop_thread_func(void * data)178 eloop_thread_func (void *data)
179 {
180     int i;
181 
182     (void) data;
183 
184     pthread_mutex_lock(&eloop_mutex);
185 
186     for (i = 0; i < eloop_start_stop_callbacks_count; i ++) {
187         eloop_start_stop_callbacks[i](true);
188     }
189 
190     __atomic_store_n(&eloop_thread_running, true, __ATOMIC_SEQ_CST);
191 
192     do {
193         eloop_call_execute();
194         i = avahi_simple_poll_iterate(eloop_poll, -1);
195     } while (i == 0 || (i < 0 && (errno == EINTR || errno == EBUSY)));
196 
197     for (i = eloop_start_stop_callbacks_count - 1; i >= 0; i --) {
198         eloop_start_stop_callbacks[i](false);
199     }
200 
201     pthread_mutex_unlock(&eloop_mutex);
202 
203     return NULL;
204 }
205 
206 /* Start event loop thread.
207  */
208 void
eloop_thread_start(void)209 eloop_thread_start (void)
210 {
211     int        rc;
212     useconds_t usec = 100;
213 
214     rc = pthread_create(&eloop_thread, NULL, eloop_thread_func, NULL);
215     if (rc != 0) {
216         log_panic(NULL, "pthread_create: %s", strerror(rc));
217     }
218 
219     /* Wait until thread is started and all start callbacks are executed */
220     while (!__atomic_load_n(&eloop_thread_running, __ATOMIC_SEQ_CST)) {
221         usleep(usec);
222         usec += usec;
223     }
224 }
225 
226 /* Stop event loop thread and wait until its termination
227  */
228 void
eloop_thread_stop(void)229 eloop_thread_stop (void)
230 {
231     if (__atomic_load_n(&eloop_thread_running, __ATOMIC_SEQ_CST)) {
232         avahi_simple_poll_quit(eloop_poll);
233         pthread_join(eloop_thread, NULL);
234         __atomic_store_n(&eloop_thread_running, false, __ATOMIC_SEQ_CST);
235     }
236 }
237 
238 /* Acquire event loop mutex
239  */
240 void
eloop_mutex_lock(void)241 eloop_mutex_lock (void)
242 {
243     pthread_mutex_lock(&eloop_mutex);
244 }
245 
246 /* Release event loop mutex
247  */
248 void
eloop_mutex_unlock(void)249 eloop_mutex_unlock (void)
250 {
251     pthread_mutex_unlock(&eloop_mutex);
252 }
253 
254 /* Wait on conditional variable under the event loop mutex
255  */
256 void
eloop_cond_wait(pthread_cond_t * cond)257 eloop_cond_wait (pthread_cond_t *cond)
258 {
259     pthread_cond_wait(cond, &eloop_mutex);
260 }
261 
262 /* Get AvahiPoll that runs in event loop thread
263  */
264 const AvahiPoll*
eloop_poll_get(void)265 eloop_poll_get (void)
266 {
267     return avahi_simple_poll_get(eloop_poll);
268 }
269 
270 /* eloop_call_pending represents a pending eloop_call
271  */
272 typedef struct {
273     void     (*func)(void*); /* Function to be called */
274     void     *data;          /* It's argument */
275     uint64_t callid;         /* For eloop_call_cancel() */
276     ll_node  node;           /* In eloop_call_pending_list */
277 } eloop_call_pending;
278 
279 /* Execute function calls deferred by eloop_call()
280  */
281 static void
eloop_call_execute(void)282 eloop_call_execute (void)
283 {
284     ll_node *node;
285 
286     while ((node = ll_pop_beg(&eloop_call_pending_list)) != NULL) {
287         eloop_call_pending *pending;
288 
289         pending = OUTER_STRUCT(node, eloop_call_pending, node);
290         pending->func(pending->data);
291         mem_free(pending);
292     }
293 }
294 
295 /* Call function on a context of event loop thread
296  * The returned value can be supplied as a `callid'
297  * parameter for the eloop_call_cancel() function
298  */
299 uint64_t
eloop_call(void (* func)(void *),void * data)300 eloop_call (void (*func)(void*), void *data)
301 {
302     eloop_call_pending *p = mem_new(eloop_call_pending, 1);
303     static uint64_t    callid;
304     uint64_t           ret;
305 
306     p->func = func;
307     p->data = data;
308 
309     pthread_mutex_lock(&eloop_mutex);
310     ret = ++ callid;
311     p->callid = ret;
312     ll_push_end(&eloop_call_pending_list, &p->node);
313     pthread_mutex_unlock(&eloop_mutex);
314 
315     avahi_simple_poll_wakeup(eloop_poll);
316 
317     return ret;
318 }
319 
320 /* Cancel pending eloop_call
321  *
322  * This is safe to cancel already finished call (at this
323  * case nothing will happen)
324  */
325 void
eloop_call_cancel(uint64_t callid)326 eloop_call_cancel (uint64_t callid)
327 {
328     ll_node *node;
329 
330     for (LL_FOR_EACH(node, &eloop_call_pending_list)) {
331         eloop_call_pending *p = OUTER_STRUCT(node, eloop_call_pending, node);
332 
333         if (p->callid == callid) {
334             ll_del(&p->node);
335             mem_free(p);
336             return;
337         }
338     }
339 }
340 
341 /* Event notifier. Calls user-defined function on a context
342  * of event loop thread, when event is triggered. This is
343  * safe to trigger the event from a context of any thread
344  * or even from a signal handler
345  */
346 struct eloop_event {
347     pollable     *p;                 /* Underlying pollable event */
348     eloop_fdpoll *fdpoll;            /* Underlying fdpoll */
349     void         (*callback)(void*); /* user-defined callback */
350     void         *data;              /* callback's argument */
351 };
352 
353 /* eloop_event eloop_fdpoll callback
354  */
355 static void
eloop_event_callback(int fd,void * data,ELOOP_FDPOLL_MASK mask)356 eloop_event_callback (int fd, void *data, ELOOP_FDPOLL_MASK mask)
357 {
358     eloop_event *event = data;
359 
360     (void) fd;
361     (void) mask;
362 
363     pollable_reset(event->p);
364     event->callback(event->data);
365 }
366 
367 /* Create new event notifier. May return NULL
368  */
369 eloop_event*
eloop_event_new(void (* callback)(void *),void * data)370 eloop_event_new (void (*callback)(void *), void *data)
371 {
372     eloop_event         *event;
373     pollable            *p;
374 
375     p = pollable_new();
376     if (p == NULL) {
377         return NULL;
378     }
379 
380     event = mem_new(eloop_event, 1);
381     event->p = p;
382     event->callback = callback;
383     event->data = data;
384 
385     event->fdpoll = eloop_fdpoll_new(pollable_get_fd(p),
386         eloop_event_callback, event);
387     eloop_fdpoll_set_mask(event->fdpoll, ELOOP_FDPOLL_READ);
388 
389     return event;
390 }
391 
392 /* Destroy event notifier
393  */
394 void
eloop_event_free(eloop_event * event)395 eloop_event_free (eloop_event *event)
396 {
397     eloop_fdpoll_free(event->fdpoll);
398     pollable_free(event->p);
399     mem_free(event);
400 }
401 
402 /* Trigger an event
403  */
404 void
eloop_event_trigger(eloop_event * event)405 eloop_event_trigger (eloop_event *event)
406 {
407     pollable_signal(event->p);
408 }
409 
410 /* Timer. Calls user-defined function after a specified
411  * interval
412  */
413 struct eloop_timer {
414     AvahiTimeout *timeout;            /* Underlying AvahiTimeout */
415     void         (*callback)(void *); /* User callback */
416     void         *data;               /* User data */
417 };
418 
419 /* eloop_timer callback for AvahiTimeout
420  */
421 static void
eloop_timer_callback(AvahiTimeout * t,void * data)422 eloop_timer_callback (AvahiTimeout *t, void *data)
423 {
424     eloop_timer *timer = data;
425 
426     (void) t;
427 
428     timer->callback(timer->data);
429     eloop_timer_cancel(timer);
430 }
431 
432 /* Create new timer. Timeout is in milliseconds
433  */
434 eloop_timer*
eloop_timer_new(int timeout,void (* callback)(void *),void * data)435 eloop_timer_new (int timeout, void (*callback)(void *), void *data)
436 {
437     const AvahiPoll *poll = eloop_poll_get();
438     eloop_timer     *timer = mem_new(eloop_timer, 1);
439     struct timeval  end;
440 
441     avahi_elapse_time(&end, timeout, 0);
442 
443     timer->timeout = poll->timeout_new(poll, &end, eloop_timer_callback, timer);
444     timer->callback = callback;
445     timer->data = data;
446 
447     return timer;
448 }
449 
450 /* Cancel a timer
451  *
452  * Caller SHOULD NOT cancel expired timer (timer with called
453  * callback) -- this is done automatically
454  */
455 void
eloop_timer_cancel(eloop_timer * timer)456 eloop_timer_cancel (eloop_timer *timer)
457 {
458     const AvahiPoll *poll = eloop_poll_get();
459 
460     poll->timeout_free(timer->timeout);
461     mem_free(timer);
462 }
463 
464 /* Convert ELOOP_FDPOLL_MASK to string. Used for logging.
465  */
466 const char*
eloop_fdpoll_mask_str(ELOOP_FDPOLL_MASK mask)467 eloop_fdpoll_mask_str (ELOOP_FDPOLL_MASK mask)
468 {
469     switch (mask & ELOOP_FDPOLL_BOTH) {
470     case 0:                  return "{--}";
471     case ELOOP_FDPOLL_READ:  return "{r-}";
472     case ELOOP_FDPOLL_WRITE: return "{-w}";
473     case ELOOP_FDPOLL_BOTH:  return "{rw}";
474     }
475 
476     return "{??}"; /* Should never happen indeed */
477 }
478 
479 /* eloop_fdpoll notifies user when file becomes
480  * readable, writable or both, depending on its
481  * event mask
482  */
483 struct eloop_fdpoll {
484     AvahiWatch        *watch;      /* Underlying AvahiWatch */
485     int               fd;          /* Underlying file descriptor */
486     ELOOP_FDPOLL_MASK mask;        /* Mask of active events */
487     void              (*callback)( /* User-defined callback */
488             int, void*, ELOOP_FDPOLL_MASK);
489     void              *data;       /* Callback's data */
490 };
491 
492 /* eloop_fdpoll callback for AvahiWatch
493  */
494 static void
eloop_fdpoll_callback(AvahiWatch * w,int fd,AvahiWatchEvent event,void * data)495 eloop_fdpoll_callback (AvahiWatch *w, int fd, AvahiWatchEvent event,
496         void *data)
497 {
498     eloop_fdpoll      *fdpoll = data;
499     ELOOP_FDPOLL_MASK mask = 0;
500 
501     (void) w;
502 
503     if ((event & AVAHI_WATCH_IN) != 0) {
504         mask |= ELOOP_FDPOLL_READ;
505     }
506 
507     if ((event & AVAHI_WATCH_OUT) != 0) {
508         mask |= ELOOP_FDPOLL_WRITE;
509     }
510 
511     fdpoll->callback(fd, fdpoll->data, mask);
512 }
513 
514 /* Create eloop_fdpoll
515  *
516  * Callback will be called, when file will be ready for read/write/both,
517  * depending on mask
518  *
519  * Initial mask value is 0, and it can be changed, using
520  * eloop_fdpoll_set_mask() function
521  */
522 eloop_fdpoll*
eloop_fdpoll_new(int fd,void (* callback)(int,void *,ELOOP_FDPOLL_MASK),void * data)523 eloop_fdpoll_new (int fd,
524         void (*callback) (int, void*, ELOOP_FDPOLL_MASK), void *data)
525 {
526     const AvahiPoll *poll = eloop_poll_get();
527     eloop_fdpoll    *fdpoll = mem_new(eloop_fdpoll, 1);
528 
529     fdpoll->fd = fd;
530     fdpoll->callback = callback;
531     fdpoll->data = data;
532 
533     eloop_poll_restart = true;
534     fdpoll->watch = poll->watch_new(poll, fd, 0, eloop_fdpoll_callback, fdpoll);
535 
536     return fdpoll;
537 }
538 
539 /* Destroy eloop_fdpoll
540  */
541 void
eloop_fdpoll_free(eloop_fdpoll * fdpoll)542 eloop_fdpoll_free (eloop_fdpoll *fdpoll)
543 {
544     const AvahiPoll *poll = eloop_poll_get();
545 
546     poll->watch_free(fdpoll->watch);
547     mem_free(fdpoll);
548 }
549 
550 /* Set eloop_fdpoll event mask. It returns a previous value of event mask
551  */
552 ELOOP_FDPOLL_MASK
eloop_fdpoll_set_mask(eloop_fdpoll * fdpoll,ELOOP_FDPOLL_MASK mask)553 eloop_fdpoll_set_mask (eloop_fdpoll *fdpoll, ELOOP_FDPOLL_MASK mask)
554 {
555     ELOOP_FDPOLL_MASK old_mask = fdpoll->mask;
556 
557     if (old_mask != mask) {
558         const AvahiPoll *poll = eloop_poll_get();
559         AvahiWatchEvent events = 0;
560 
561         if ((mask & ELOOP_FDPOLL_READ) != 0) {
562             events |= AVAHI_WATCH_IN;
563         }
564 
565         if ((mask & ELOOP_FDPOLL_WRITE) != 0) {
566             events |= AVAHI_WATCH_OUT;
567         }
568 
569         fdpoll->mask = mask;
570         poll->watch_update(fdpoll->watch, events);
571     }
572 
573     return old_mask;
574 }
575 
576 /* Format error string, as printf() does and save result
577  * in the memory, owned by the event loop
578  *
579  * Caller should not free returned string. This is safe
580  * to use the returned string as an argument to the
581  * subsequent eloop_eprintf() call.
582  *
583  * The returned string remains valid until next call
584  * to eloop_eprintf(), which makes it usable to
585  * report errors up by the stack. However, it should
586  * not be assumed, that the string will remain valid
587  * on a next eloop roll, so don't save this string
588  * anywhere, if you need to do so, create a copy!
589  */
590 error
eloop_eprintf(const char * fmt,...)591 eloop_eprintf(const char *fmt, ...)
592 {
593     va_list ap;
594     char    buf[sizeof(eloop_estring)];
595 
596     va_start(ap, fmt);
597     vsnprintf(buf, sizeof(buf), fmt, ap);
598     strcpy(eloop_estring, buf);
599     va_end(ap);
600 
601     return ERROR(eloop_estring);
602 }
603 
604 /* vim:ts=8:sw=4:et
605  */
606